Twitch chat now supports subscriptions

- refactored code to be cleaner and more modularized

Updated scrolling on twitch chat to actually scroll to the bottom with new messages

Fast forwarding in videos with a twitch chat is now faster and provides a smoother transition
This commit is contained in:
Isaac Abadi
2020-12-10 21:04:53 -05:00
parent ff1bb8dee1
commit 9de403245b
8 changed files with 107 additions and 80 deletions

View File

@@ -1,7 +1,8 @@
<div class="chat-container" #scrollContainer *ngIf="visible_chat">
<div style="width: 250px; text-align: center;"><strong>Twitch Chat</strong></div>
<div style="max-width: 250px" *ngFor="let chat of visible_chat">
<div #chat style="max-width: 250px" *ngFor="let chat of visible_chat; let last = last">
{{chat.timestamp_str}} - <strong [style.color]="chat.user_color ? chat.user_color : null">{{chat.name}}</strong>: {{chat.message}}
{{last ? scrollToBottom() : ''}}
</div>
</div>

View File

@@ -21,9 +21,11 @@ export class TwitchChatComponent implements OnInit, AfterViewInit {
scrollContainer = null;
@Input() db_file = null;
@Input() sub = null;
@Input() current_timestamp = null;
@ViewChild('scrollContainer') scrollRef: ElementRef;
@ViewChildren('chat') chat: QueryList<any>;
constructor(private postsService: PostsService) { }
@@ -35,22 +37,31 @@ export class TwitchChatComponent implements OnInit, AfterViewInit {
}
private isUserNearBottom(): boolean {
const threshold = 300;
const threshold = 150;
const position = this.scrollContainer.scrollTop + this.scrollContainer.offsetHeight;
const height = this.scrollContainer.scrollHeight;
return position > height - threshold;
}
scrollToBottom = () => {
this.scrollContainer.scrollTop = this.scrollContainer.scrollHeight;
scrollToBottom = (force_scroll) => {
if (force_scroll || this.isUserNearBottom()) {
this.scrollContainer.scrollTop = this.scrollContainer.scrollHeight;
}
}
addNewChatMessages() {
const next_chat_index = this.getIndexOfNextChat();
if (!this.scrollContainer) {
this.scrollContainer = this.scrollRef.nativeElement;
}
if (this.current_chat_index === null) {
this.current_chat_index = this.getIndexOfNextChat();
this.current_chat_index = next_chat_index;
}
if (Math.abs(next_chat_index - this.current_chat_index) > 25) {
this.visible_chat = [];
this.current_chat_index = next_chat_index - 25;
setTimeout(() => this.scrollToBottom(true), 100);
}
const latest_chat_timestamp = this.visible_chat.length ? this.visible_chat[this.visible_chat.length - 1]['timestamp'] : 0;
@@ -59,9 +70,6 @@ export class TwitchChatComponent implements OnInit, AfterViewInit {
if (this.full_chat[i]['timestamp'] >= latest_chat_timestamp && this.full_chat[i]['timestamp'] <= this.current_timestamp) {
this.visible_chat.push(this.full_chat[i]);
this.current_chat_index = i;
if (this.isUserNearBottom()) {
this.scrollToBottom();
}
} else if (this.full_chat[i]['timestamp'] > this.current_timestamp) {
break;
}
@@ -74,7 +82,7 @@ export class TwitchChatComponent implements OnInit, AfterViewInit {
}
getFullChat() {
this.postsService.getFullTwitchChat(this.db_file.id, this.db_file.isAudio ? 'audio' : 'video', null).subscribe(res => {
this.postsService.getFullTwitchChat(this.db_file.id, this.db_file.isAudio ? 'audio' : 'video', null, this.sub).subscribe(res => {
this.chat_response_received = true;
if (res['chat']) {
this.initializeChatCheck(res['chat']);
@@ -82,11 +90,6 @@ export class TwitchChatComponent implements OnInit, AfterViewInit {
});
}
renewChat() {
this.visible_chat = [];
this.current_chat_index = this.getIndexOfNextChat();
}
downloadTwitchChat() {
this.downloading_chat = true;
let vodId = this.db_file.url.split('videos/').length > 1 && this.db_file.url.split('videos/')[1];
@@ -94,7 +97,7 @@ export class TwitchChatComponent implements OnInit, AfterViewInit {
if (!vodId) {
this.postsService.openSnackBar('VOD url for this video is not supported. VOD ID must be after "twitch.tv/videos/"');
}
this.postsService.downloadTwitchChat(this.db_file.id, this.db_file.isAudio ? 'audio' : 'video', vodId, null).subscribe(res => {
this.postsService.downloadTwitchChat(this.db_file.id, this.db_file.isAudio ? 'audio' : 'video', vodId, null, this.sub).subscribe(res => {
if (res['chat']) {
this.initializeChatCheck(res['chat']);
} else {

View File

@@ -15,7 +15,7 @@
</div>
<mat-drawer #drawer class="example-sidenav" mode="side" position="end" [opened]="db_file && db_file['chat_exists']">
<ng-container *ngIf="api_ready && db_file && db_file.url.includes('twitch.tv/videos/')">
<app-twitch-chat #twitchchat [db_file]="db_file" [current_timestamp]="api.currentTime"></app-twitch-chat>
<app-twitch-chat #twitchchat [db_file]="db_file" [current_timestamp]="api.currentTime" [sub]="subscription"></app-twitch-chat>
</ng-container>
</mat-drawer>

View File

@@ -39,6 +39,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
type: string;
id = null; // used for playlists (not subscription)
uid = null; // used for non-subscription files (audio, video, playlist)
subscription = null;
subscriptionName = null;
subPlaylist = null;
uuid = null; // used for sharing in multi-user mode, uuid is the user that downloaded the video
@@ -180,6 +181,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
getSubscription() {
this.postsService.getSubscription(null, this.subscriptionName).subscribe(res => {
const subscription = res['subscription'];
this.subscription = subscription;
if (this.fileNames) {
subscription.videos.forEach(video => {
if (video['id'] === this.fileNames[0]) {
@@ -276,12 +278,6 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
this.api = api;
this.api_ready = true;
this.api.subscriptions.seeked.subscribe(data => {
if (this.twitchChat) {
this.twitchChat.renewChat();
}
});
// checks if volume has been previously set. if so, use that as default
if (localStorage.getItem('player_volume')) {
this.api.volume = parseFloat(localStorage.getItem('player_volume'));

View File

@@ -234,12 +234,12 @@ export class PostsService implements CanActivate {
return this.http.post(this.path + 'getAllFiles', {}, this.httpOptions);
}
getFullTwitchChat(id, type, uuid = null) {
return this.http.post(this.path + 'getFullTwitchChat', {id: id, type: type, uuid: uuid}, this.httpOptions);
getFullTwitchChat(id, type, uuid = null, sub = null) {
return this.http.post(this.path + 'getFullTwitchChat', {id: id, type: type, uuid: uuid, sub: sub}, this.httpOptions);
}
downloadTwitchChat(id, type, vodId, uuid = null) {
return this.http.post(this.path + 'downloadTwitchChatByVODID', {id: id, type: type, vodId: vodId, uuid: uuid}, this.httpOptions);
downloadTwitchChat(id, type, vodId, uuid = null, sub = null) {
return this.http.post(this.path + 'downloadTwitchChatByVODID', {id: id, type: type, vodId: vodId, uuid: uuid, sub: sub}, this.httpOptions);
}
downloadFileFromServer(fileName, type, outputName = null, fullPathProvided = null, subscriptionName = null, subPlaylist = null,