mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-04-19 01:01:28 +03:00
Users can now stream videos concurrently with other users with the new concurrent stream component
This commit is contained in:
@@ -86,6 +86,7 @@ import { EditCategoryDialogComponent } from './dialogs/edit-category-dialog/edit
|
|||||||
import { TwitchChatComponent } from './components/twitch-chat/twitch-chat.component';
|
import { TwitchChatComponent } from './components/twitch-chat/twitch-chat.component';
|
||||||
import { LinkifyPipe, SeeMoreComponent } from './components/see-more/see-more.component';
|
import { LinkifyPipe, SeeMoreComponent } from './components/see-more/see-more.component';
|
||||||
import { H401Interceptor } from './http.interceptor';
|
import { H401Interceptor } from './http.interceptor';
|
||||||
|
import { ConcurrentStreamComponent } from './components/concurrent-stream/concurrent-stream.component';
|
||||||
|
|
||||||
registerLocaleData(es, 'es');
|
registerLocaleData(es, 'es');
|
||||||
|
|
||||||
@@ -134,7 +135,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
CustomPlaylistsComponent,
|
CustomPlaylistsComponent,
|
||||||
EditCategoryDialogComponent,
|
EditCategoryDialogComponent,
|
||||||
TwitchChatComponent,
|
TwitchChatComponent,
|
||||||
SeeMoreComponent
|
SeeMoreComponent,
|
||||||
|
ConcurrentStreamComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<div class="buttons-container">
|
||||||
|
<button (click)="startWatching()" *ngIf="!watch_together_clicked" mat-flat-button>Watch together</button>
|
||||||
|
<button (click)="startServer()" *ngIf="watch_together_clicked && !started && server_mode && server_already_exists === false" mat-flat-button>Start stream</button>
|
||||||
|
<button (click)="startClient()" *ngIf="watch_together_clicked && !started && server_already_exists === true" mat-flat-button>Join stream</button>
|
||||||
|
<button style="margin-left: 10px;" (click)="stop()" *ngIf="watch_together_clicked" mat-flat-button>Stop</button>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
.buttons-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ConcurrentStreamComponent } from './concurrent-stream.component';
|
||||||
|
|
||||||
|
describe('ConcurrentStreamComponent', () => {
|
||||||
|
let component: ConcurrentStreamComponent;
|
||||||
|
let fixture: ComponentFixture<ConcurrentStreamComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ ConcurrentStreamComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ConcurrentStreamComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
|
import { PostsService } from 'app/posts.services';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-concurrent-stream',
|
||||||
|
templateUrl: './concurrent-stream.component.html',
|
||||||
|
styleUrls: ['./concurrent-stream.component.scss']
|
||||||
|
})
|
||||||
|
export class ConcurrentStreamComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() server_mode = false;
|
||||||
|
@Input() playback_timestamp;
|
||||||
|
@Input() playing;
|
||||||
|
@Input() uid;
|
||||||
|
|
||||||
|
@Output() setPlaybackTimestamp = new EventEmitter<any>();
|
||||||
|
@Output() togglePlayback = new EventEmitter<boolean>();
|
||||||
|
@Output() setPlaybackRate = new EventEmitter<number>();
|
||||||
|
|
||||||
|
started = false;
|
||||||
|
server_started = false;
|
||||||
|
watch_together_clicked = false;
|
||||||
|
|
||||||
|
server_already_exists = null;
|
||||||
|
|
||||||
|
check_timeout: any;
|
||||||
|
update_timeout: any;
|
||||||
|
|
||||||
|
PLAYBACK_TIMESTAMP_DIFFERENCE_THRESHOLD_PLAYBACK_MODIFICATION = 0.5;
|
||||||
|
PLAYBACK_TIMESTAMP_DIFFERENCE_THRESHOLD_SKIP = 2;
|
||||||
|
|
||||||
|
PLAYBACK_MODIFIER = 0.1;
|
||||||
|
|
||||||
|
playback_rate_modified = false;
|
||||||
|
|
||||||
|
constructor(private postsService: PostsService) { }
|
||||||
|
|
||||||
|
// flow: click start watching -> check for available stream to enable join button and if user, display "start stream"
|
||||||
|
// users who join a stream will send continuous requests for info on playback
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
startServer() {
|
||||||
|
this.started = true;
|
||||||
|
this.server_started = true;
|
||||||
|
this.update_timeout = setInterval(() => {
|
||||||
|
this.updateStream();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStream() {
|
||||||
|
this.postsService.updateConcurrentStream(this.uid, this.playback_timestamp, Date.now()/1000, this.playing).subscribe(res => {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startClient() {
|
||||||
|
this.started = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStream() {
|
||||||
|
if (this.server_started) { return; }
|
||||||
|
const current_playback_timestamp = this.playback_timestamp;
|
||||||
|
const current_unix_timestamp = Date.now()/1000;
|
||||||
|
this.postsService.checkConcurrentStream(this.uid).subscribe(res => {
|
||||||
|
const stream = res['stream'];
|
||||||
|
|
||||||
|
if (!stream) {
|
||||||
|
this.server_already_exists = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.server_already_exists = true;
|
||||||
|
|
||||||
|
// check whether client has joined the stream
|
||||||
|
if (!this.started) { return; }
|
||||||
|
|
||||||
|
if (!stream['playing'] && this.playing) {
|
||||||
|
// tell client to pause and set the timestamp to sync
|
||||||
|
this.togglePlayback.emit(false);
|
||||||
|
this.setPlaybackTimestamp.emit(stream['playback_timestamp']);
|
||||||
|
} else if (stream['playing']) {
|
||||||
|
// sync unpause state
|
||||||
|
if (!this.playing) { this.togglePlayback.emit(true); }
|
||||||
|
|
||||||
|
// sync time
|
||||||
|
const zeroed_local_unix_timestamp = current_unix_timestamp - current_playback_timestamp;
|
||||||
|
const zeroed_server_unix_timestamp = stream['unix_timestamp'] - stream['playback_timestamp'];
|
||||||
|
|
||||||
|
const seconds_behind_locally = zeroed_local_unix_timestamp - zeroed_server_unix_timestamp;
|
||||||
|
|
||||||
|
if (Math.abs(seconds_behind_locally) > this.PLAYBACK_TIMESTAMP_DIFFERENCE_THRESHOLD_SKIP) {
|
||||||
|
// skip to playback timestamp because the difference is too high
|
||||||
|
this.setPlaybackTimestamp.emit(this.playback_timestamp + seconds_behind_locally + 0.3);
|
||||||
|
this.playback_rate_modified = false;
|
||||||
|
} else if (!this.playback_rate_modified && Math.abs(seconds_behind_locally) > this.PLAYBACK_TIMESTAMP_DIFFERENCE_THRESHOLD_PLAYBACK_MODIFICATION) {
|
||||||
|
// increase playback speed to avoid skipping
|
||||||
|
let seconds_to_wait = (Math.abs(seconds_behind_locally)/this.PLAYBACK_MODIFIER);
|
||||||
|
seconds_to_wait += 0.3/this.PLAYBACK_MODIFIER;
|
||||||
|
|
||||||
|
this.playback_rate_modified = true;
|
||||||
|
|
||||||
|
if (seconds_behind_locally > 0) {
|
||||||
|
// increase speed
|
||||||
|
this.setPlaybackRate.emit(1 + this.PLAYBACK_MODIFIER);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setPlaybackRate.emit(1);
|
||||||
|
this.playback_rate_modified = false;
|
||||||
|
}, seconds_to_wait * 1000);
|
||||||
|
} else {
|
||||||
|
// decrease speed
|
||||||
|
this.setPlaybackRate.emit(1 - this.PLAYBACK_MODIFIER);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setPlaybackRate.emit(1);
|
||||||
|
this.playback_rate_modified = false;
|
||||||
|
}, seconds_to_wait * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startWatching() {
|
||||||
|
this.watch_together_clicked = true;
|
||||||
|
this.check_timeout = setInterval(() => {
|
||||||
|
this.checkStream();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (this.check_timeout) { clearInterval(this.check_timeout); }
|
||||||
|
if (this.update_timeout) { clearInterval(this.update_timeout); }
|
||||||
|
this.started = false;
|
||||||
|
this.server_started = false;
|
||||||
|
this.watch_together_clicked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user