diff --git a/packages/frontend/src/app/app-routing.module.ts b/packages/frontend/src/app/app-routing.module.ts index a7c12dd..a8f3429 100644 --- a/packages/frontend/src/app/app-routing.module.ts +++ b/packages/frontend/src/app/app-routing.module.ts @@ -9,7 +9,6 @@ import { SignUpComponent } from './auth/sign-up/sign-up.component'; import { PageNotFoundComponent } from './navigation/page-not-found/page-not-found.component'; import { AuthGuardService as AuthGuard } from './auth/auth.guard'; import { AdminGuardService as AdminGuard } from './admin/admin.guard'; -import { PlayerComponent } from './player/player.component'; import { AdminMediasComponent } from './admin/admin-medias/admin-medias.component'; import { AdminUsersComponent } from './admin/admin-users/admin-users.component'; import { SearchResultsComponent } from './pages/search-results/search-results.component'; @@ -18,6 +17,7 @@ import { NotActivatedComponent } from './navigation/not-activated/not-activated. import { AlreadyActivatedGuard } from './auth/already-activated.guard'; import { MyListComponent } from './pages/my-list/my-list.component'; import { CategoryResultsComponent } from './pages/category-results/category-results.component'; +import { IllegalComponent } from "./player/illegal.component"; const routes: Routes = [ { @@ -96,13 +96,8 @@ const routes: Routes = [ component: SignUpComponent, }, { - path: 'play/:mediaId', - component: PlayerComponent, - canActivate: [AuthGuard, ActivatedGuard], - }, - { - path: 'play/:mediaId/:seasonIndex/:episodeIndex', - component: PlayerComponent, + path: 'illegal', + component: IllegalComponent, canActivate: [AuthGuard, ActivatedGuard], }, { path: '**', redirectTo: 'not-found' }, diff --git a/packages/frontend/src/app/app.module.ts b/packages/frontend/src/app/app.module.ts index e6c47de..a99f909 100644 --- a/packages/frontend/src/app/app.module.ts +++ b/packages/frontend/src/app/app.module.ts @@ -11,15 +11,14 @@ import { AuthModule } from './auth/auth.module'; import { AdminModule } from './admin/admin.module'; import { interceptors } from './api/interceptors'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { PlayerComponent } from './player/player.component'; import { NgChartsModule } from 'ng2-charts'; import { ServiceWorkerModule } from '@angular/service-worker'; import { environment } from '../environments/environment'; -import { PlayNextMediaComponent } from './player/play-next-media/play-next-media.component'; import { UserModule } from './user/user.module'; +import { IllegalComponent } from "./player/illegal.component"; @NgModule({ - declarations: [AppComponent, PlayerComponent, PlayNextMediaComponent], + declarations: [AppComponent, IllegalComponent], imports: [ BrowserModule, AppRoutingModule, diff --git a/packages/frontend/src/app/player/illegal.component.html b/packages/frontend/src/app/player/illegal.component.html new file mode 100644 index 0000000..17d753c --- /dev/null +++ b/packages/frontend/src/app/player/illegal.component.html @@ -0,0 +1,33 @@ +
+
+
+
+
+
+

+ 👮 Le contenu n'est pas disponible 👮 +

+
+
+
+
+ +
+
diff --git a/packages/frontend/src/app/player/illegal.component.ts b/packages/frontend/src/app/player/illegal.component.ts new file mode 100644 index 0000000..050e0f2 --- /dev/null +++ b/packages/frontend/src/app/player/illegal.component.ts @@ -0,0 +1,40 @@ +import { Component } from '@angular/core'; +import { animate, style, transition, trigger } from '@angular/animations'; + +@Component({ + selector: 'app-not-activated', + templateUrl: './illegal.component.html', + animations: [ + trigger('onNotActivated', [ + transition(':enter', [ + style({ + opacity: 0, + transform: 'TranslateY(-100%) Scale(0.5)', + }), + animate( + '1s ease', + style({ + opacity: 1, + transform: 'TranslateY(0) Scale(1)', + }), + ), + ]), + ]), + trigger('onReturnToMenuButton', [ + transition(':enter', [ + style({ + opacity: 0, + transform: 'Scale(0.5)', + }), + animate( + '1s 0.5s ease', + style({ + opacity: 1, + transform: 'Scale(1)', + }), + ), + ]), + ]), + ], +}) +export class IllegalComponent {} diff --git a/packages/frontend/src/app/player/play-next-media/play-next-media.component.html b/packages/frontend/src/app/player/play-next-media/play-next-media.component.html deleted file mode 100644 index 8fc8cd2..0000000 --- a/packages/frontend/src/app/player/play-next-media/play-next-media.component.html +++ /dev/null @@ -1,31 +0,0 @@ -
-
-
- - {{ vendorTag }} -
-

{{ media.data.title }}

-
-
-
-

Voir le film

-
-
-
diff --git a/packages/frontend/src/app/player/play-next-media/play-next-media.component.ts b/packages/frontend/src/app/player/play-next-media/play-next-media.component.ts deleted file mode 100644 index 959eb94..0000000 --- a/packages/frontend/src/app/player/play-next-media/play-next-media.component.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { - FeaturedType, - MediaWithType, - MediaWithTypeAndFeatured, -} from '../../shared/models/media.model'; -import { IconDefinition } from '@fortawesome/free-regular-svg-icons'; -import { PlayerService } from '../../shared/services/player.service'; -import { MediasService } from '../../shared/services/medias.service'; - -@Component({ - selector: 'app-play-next-media', - templateUrl: './play-next-media.component.html', -}) -export class PlayNextMediaComponent implements OnInit { - @Input() media: MediaWithTypeAndFeatured | null = null; - - vendorTag = ''; - vendorIcon: IconDefinition | null = null; - - constructor( - private readonly playerService: PlayerService, - private readonly mediasService: MediasService, - ) {} - - getTagAndIconFromFeatured(featured: FeaturedType): [string, IconDefinition] { - return this.mediasService.getTagAndIconFromFeatured(featured); - } - - mediaPlayLink(media: MediaWithType) { - return `/play/${media.data._id}${media.mediaType === 'tv' ? '/1/1' : ''}`; - } - - onPlay(event: MouseEvent) { - if (this.media) { - this.playerService.play({ - mediaId: this.media.data._id, - x: event.clientX, - y: event.clientY, - }); - } - } - - ngOnInit(): void { - if (this.media) { - [this.vendorTag, this.vendorIcon] = this.getTagAndIconFromFeatured( - this.media.featured, - ); - } - } -} diff --git a/packages/frontend/src/app/player/player.component.html b/packages/frontend/src/app/player/player.component.html deleted file mode 100644 index 7b50c47..0000000 --- a/packages/frontend/src/app/player/player.component.html +++ /dev/null @@ -1,54 +0,0 @@ -
-
- -
- -
-
-
diff --git a/packages/frontend/src/app/player/player.component.ts b/packages/frontend/src/app/player/player.component.ts deleted file mode 100644 index e665c57..0000000 --- a/packages/frontend/src/app/player/player.component.ts +++ /dev/null @@ -1,481 +0,0 @@ -import { - AfterViewInit, - Component, - ElementRef, - HostListener, - OnDestroy, - ViewChild, -} from '@angular/core'; -import { - MediaWithType, - MediaWithTypeAndFeatured, -} from '../shared/models/media.model'; -import { ActivatedRoute, Router } from '@angular/router'; -import { filter, map, Observable, sampleTime, switchMap } from 'rxjs'; -import { MediasService } from '../shared/services/medias.service'; - -import * as videojs from 'video.js'; -import { VideoJsPlayer } from 'video.js'; -import { environment } from '../../environments/environment'; -import { animate, style, transition, trigger } from '@angular/animations'; -import { PlayerService } from '../shared/services/player.service'; -import { PreviousRouteService } from '../shared/services/previous-route.service'; -import { - faChevronLeft, - faChevronRight, - faLeftLong, -} from '@fortawesome/free-solid-svg-icons'; -import { TitleService } from '../shared/services/title.service'; -import { UsersService } from '../shared/services/users.service'; -import { AuthService } from '../shared/services/auth.service'; -import { PlayedMedia } from '../shared/models/played-media.model'; - -@Component({ - selector: 'app-player', - templateUrl: './player.component.html', - animations: [ - trigger('onPlay', [ - transition(':enter', [ - style({ - transform: 'Scale(2)', - opacity: 0, - }), - animate( - '2s ease', - style({ - transform: 'Scale(1)', - opacity: 1, - }), - ), - ]), - ]), - trigger('onContent', [ - transition( - ':enter', - [ - style({ - transform: 'TranslateX(100%)', - opacity: 0, - }), - animate( - '0.5s {{delay}}ms ease', - style({ - transform: 'TranslateX(0%)', - opacity: 1, - }), - ), - ], - { params: { delay: '500' } }, - ), - transition(':leave', [ - style({ - transform: 'TranslateX(0%)', - opacity: 1, - }), - animate( - '0.5s ease', - style({ - transform: 'TranslateX(100%)', - opacity: 0, - }), - ), - ]), - ]), - ], -}) -export class PlayerComponent implements AfterViewInit, OnDestroy { - media: MediaWithType | null = null; - - private player: VideoJsPlayer | null = null; - source: string | null = null; - - returnUrl = { route: '/home', params: {} }; - - @ViewChild('player') playerElement: ElementRef | null = null; - @ViewChild('customPlayer') - customPlayerElement: ElementRef | null = null; - - featuredMedias: MediaWithTypeAndFeatured[] = []; - - cueCount = 0; - cueSet = false; - - displayFeatured = false; - - leftIcon = faLeftLong; - chevronLeftIcon = faChevronLeft; - chevronRightIcon = faChevronRight; - - constructor( - private readonly route: ActivatedRoute, - private readonly playerService: PlayerService, - private readonly mediasService: MediasService, - private readonly usersService: UsersService, - private readonly auth: AuthService, - private readonly previousRouteService: PreviousRouteService, - private readonly router: Router, - private readonly title: TitleService, - ) {} - - @HostListener('window:keyup', ['$event']) - keyEvent(event: KeyboardEvent) { - if (this.player) { - switch (event.key) { - case 'ArrowLeft': - this.player.currentTime(this.player.currentTime() - 5); - break; - case 'ArrowRight': - this.player.currentTime(this.player.currentTime() + 5); - break; - case 'ArrowUp': - this.player.volume(this.player.volume() + 0.1); - break; - case 'ArrowDown': - this.player.volume(this.player.volume() - 0.1); - break; - case ' ': - if (this.player?.paused()) { - this.player?.play(); - } else { - this.player?.pause(); - } - break; - } - } - } - - addOffset(offset: number) { - if (this.player && !this.cueSet) { - Array.from(this.player.textTracks()).forEach((track) => { - if (track.mode === 'showing') { - if (track.cues) { - this.cueCount++; - if (this.cueCount === 2) { - this.cueSet = true; - Array.from(track.cues).forEach((cue) => { - cue.startTime += offset || 0.5; - cue.endTime += offset || 0.5; - }); - } - } - } - }); - } - } - - getLangFromCode(trigram: string) { - const code = trigram.toLowerCase(); - if (['fre', 'vff'].includes(code)) { - return 'Français'; - } else if (['eng'].includes(code)) { - return 'Anglais'; - } else if (['jpn'].includes(code)) { - return 'Japonais'; - } else if (['vfq'].includes(code)) { - return 'Québécois'; - } else if (['spa'].includes(code)) { - return 'Espagnol'; - } else if (['por'].includes(code)) { - return 'Portugais'; - } else if (['ita'].includes(code)) { - return 'Italien'; - } else if (['ger'].includes(code)) { - return 'Allemand'; - } else if (['rus'].includes(code)) { - return 'Russe'; - } else if (['chi'].includes(code)) { - return 'Chinois'; - } - - return code; - } - - setLanguageAsName() { - if (this.player) { - const audioTrackList = this.player.audioTracks(); - for (let i = 0; i < audioTrackList.length; i++) { - (audioTrackList[i] as any).label = this.getLangFromCode( - audioTrackList[i].language, - ); - } - } - } - - setAudioTrackByIndex(index: number) { - if (this.player) { - const audioTrackList = this.player.audioTracks(); - for (let i = 0; i < audioTrackList.length; i++) - audioTrackList[i].enabled = i === index; - } - } - - setTextTrackByIndex(index: number) { - if (this.player) { - const textTrackList = this.player.textTracks(); - for (let i = 0; i < textTrackList.length; i++) - textTrackList[i].mode = i === index ? 'showing' : 'disabled'; - } - } - - getAudioTrackIndex() { - if (this.player) { - const audioTrackList = this.player.audioTracks(); - for (let i = 0; i < audioTrackList.length; i++) - if (audioTrackList[i].enabled) return i; - } - - return -1; - } - - onAudioTrackChange() { - return new Observable((observer) => - this.player?.audioTracks().on('change', () => observer.next()), - ).pipe(map(() => this.getAudioTrackIndex())); - } - - onTimeUpdate() { - return new Observable((observer) => - this.player?.on('timeupdate', () => observer.next()), - ).pipe( - sampleTime(2000), - filter(() => !this.player?.paused()), - map(() => Math.floor(this.player?.currentTime() || 0)), - ); - } - - getTextTrackIndex() { - if (this.player) { - const textTrackList = this.player.textTracks(); - for (let i = 0; i < textTrackList.length; i++) - if (textTrackList[i].mode === 'showing') return i; - } - - return -1; - } - - onTextTrackChange() { - return new Observable((observer) => - this.player - ?.textTracks() - .addEventListener('change', () => observer.next()), - ).pipe(map(() => this.getTextTrackIndex())); - } - - getLocation( - media: MediaWithType, - seasonIndex?: number, - episodeIndex?: number, - ) { - if (seasonIndex === undefined || episodeIndex === undefined) { - return media.data.fileInfos?.location; - } else if (media.data.tvs) { - const episodes = media.data.tvs[seasonIndex - 1].episodes; - - if (episodes) { - return episodes[episodeIndex - 1].fileInfos?.location; - } - } - - return null; - } - - onPlayedMediaUpdated(playedMedia: PlayedMedia) { - if (this.media) { - this.auth.updateUserProfile({ - playedMedias: this.usersService.getPlayedMediasFromPlayedMedia( - this.auth.user?.playedMedias || [], - playedMedia, - ), - }); - } - } - - ngOnDestroy() { - this.player?.dispose(); - } - - ngAfterViewInit() { - const prevRoute = this.previousRouteService.previousRoute; - if (prevRoute !== this.router.url) { - this.returnUrl.route = - this.router.parseUrl(prevRoute).root.children[ - 'primary' - ].segments[0].path; - this.returnUrl.params = this.router.parseUrl(prevRoute).queryParams; - } - - this.mediasService - .getFeaturedMedias() - .subscribe((medias) => (this.featuredMedias = medias)); - - this.route.paramMap - .pipe( - switchMap((params) => - this.mediasService.getMedia(params.get('mediaId') || '').pipe( - map((media) => ({ - media, - seasonIndex: params.get('seasonIndex'), - episodeIndex: params.get('episodeIndex'), - })), - ), - ), - ) - .subscribe(({ media, episodeIndex, seasonIndex }) => { - const tvIndexes = - seasonIndex && seasonIndex ? `${seasonIndex}/${episodeIndex}/` : ''; - const location = - this.getLocation( - media, - seasonIndex ? parseInt(seasonIndex) : undefined, - episodeIndex ? parseInt(episodeIndex) : undefined, - ) || 'default'; - this.media = media; - this.source = this.media?.data._id - ? `${environment.apiUrl}/medias/stream/${location}/${this.media?.data._id}/${tvIndexes}master.m3u8` - : ''; - - this.title.setTitle(this.media?.data.title || ''); - - videojs.default.addLanguage('fr', { - Play: 'Reprendre', - Pause: 'Pause', - 'Current Time': 'Temps actuel', - Duration: 'Durée', - 'Remaining Time': 'Temps restant', - 'Stream Type': 'Type de flux', - LIVE: 'EN DIRECT', - Loaded: 'Chargé', - Progress: 'Progression', - Fullscreen: 'Plein écran', - 'Non-Fullscreen': 'Plein écran', - Mute: 'Muet', - Unmute: 'Non muet', - 'Playback Rate': 'Vitesse de lecture', - Subtitles: 'Sous-titres', - 'subtitles off': 'sous-titres désactivés', - Captions: 'Sous-titres', - 'captions off': 'sous-titres désactivés', - Chapters: 'Chapitres', - 'Close Modal Dialog': 'Fermer la boîte de dialogue', - 'You aborted the video playback': - 'Vous avez interrompu la lecture de la vidéo.', - 'A network error caused the video download to fail part-way.': - 'Une erreur de réseau a interrompu la lecture de la vidéo.', - 'The video could not be loaded, either because the server or network failed or because the format is not supported.': - "La vidéo n'a pas pu être chargée à cause d'une erreur de réseau ou de format non supporté.", - 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support.': - "La lecture de la vidéo a été interrompue à cause d'un problème de corruption ou parce que la vidéo utilise des fonctionnalités non supportées par votre navigateur.", - 'No compatible source was found for this video.': - "Aucune source compatible n'a été trouvée pour cette vidéo.", - }); - - this.player = videojs.default( - this.playerElement?.nativeElement as Element, - { - bigPlayButton: false, - autoplay: true, - language: 'fr', - textTrackSettings: false as any, - controlBar: { - liveDisplay: false, - seekToLive: false, - pictureInPictureToggle: false, - }, - }, - ); - - this.player.src({ - src: this.source, - html5: { - hls: { - overrideNative: videojs.default.browser.IS_SAFARI, - }, - nativeVideoTracks: !videojs.default.browser.IS_SAFARI, - nativeAudioTracks: !videojs.default.browser.IS_SAFARI, - nativeTextTracks: !videojs.default.browser.IS_SAFARI, - }, - type: 'application/x-mpegURL', - withCredentials: true, - } as any); - - const si = seasonIndex ? parseInt(seasonIndex) : undefined; - const ei = episodeIndex ? parseInt(episodeIndex) : undefined; - - const indexesParams = si && ei ? { si, ei } : {}; - - this.customPlayerElement?.nativeElement.childNodes.forEach((node) => { - this.playerElement?.nativeElement.parentElement?.insertBefore( - node, - this.playerElement?.nativeElement.parentElement?.getElementsByClassName( - 'vjs-control-bar', - )[0], - ); - }); - - this.player.on('loadeddata', () => { - this.setLanguageAsName(); - - this.playerService - .getPlayerStatus(this.media?.data._id || '', si, ei) - .subscribe((data) => { - if (data.currentTime !== undefined && data.currentTime >= 0) { - this.player?.currentTime(data.currentTime); - } - - if (data.audioTrack !== undefined && data.subtitleTrack !== -1) { - this.setAudioTrackByIndex(data.audioTrack); - } - - if ( - data.subtitleTrack !== undefined && - data.subtitleTrack !== -1 - ) { - this.setTextTrackByIndex(data.subtitleTrack); - } - }); - - this.onAudioTrackChange() - .pipe( - switchMap((index) => - this.playerService.track({ - mediaId: this.media?.data._id || '', - ...indexesParams, - ai: index, - }), - ), - ) - .subscribe(); - this.onTextTrackChange() - .pipe( - switchMap((index) => - this.playerService.track({ - mediaId: this.media?.data._id || '', - ...indexesParams, - ti: index, - }), - ), - ) - .subscribe(); - this.onTimeUpdate() - .pipe( - switchMap((time) => - this.playerService.track({ - mediaId: this.media?.data._id || '', - ...indexesParams, - time, - }), - ), - ) - .subscribe(); - }); - - this.player.on('ended', () => (this.displayFeatured = true)); - - this.player.on('texttrackchange', () => this.addOffset(1.2)); - this.player.on('seeked', () => { - this.cueCount = 0; - this.cueSet = false; - }); - }); - } -} diff --git a/packages/frontend/src/app/shared/services/player.service.ts b/packages/frontend/src/app/shared/services/player.service.ts index 1a53763..f5c5e82 100644 --- a/packages/frontend/src/app/shared/services/player.service.ts +++ b/packages/frontend/src/app/shared/services/player.service.ts @@ -94,11 +94,8 @@ export class PlayerService { navigate() { if (this.playerData) { - const { seasonIndex, episodeIndex, mediaId } = this.playerData; this.router.navigate([ - '/play', - mediaId, - ...(seasonIndex && episodeIndex ? [seasonIndex, episodeIndex] : []), + '/illegal', ]); } this.playerData = null; diff --git a/packages/frontend/src/assets/img/illegal.gif b/packages/frontend/src/assets/img/illegal.gif new file mode 100644 index 0000000..f1dc8bc Binary files /dev/null and b/packages/frontend/src/assets/img/illegal.gif differ