diff options
7 files changed, 54 insertions, 34 deletions
diff --git a/client/src/app/+videos/+video-watch/video-watch-playlist.component.html b/client/src/app/+videos/+video-watch/video-watch-playlist.component.html index 246ef83cf..c270142a3 100644 --- a/client/src/app/+videos/+video-watch/video-watch-playlist.component.html +++ b/client/src/app/+videos/+video-watch/video-watch-playlist.component.html | |||
@@ -1,4 +1,7 @@ | |||
1 | <div *ngIf="playlist && video" class="playlist" myInfiniteScroller [autoInit]="true" [onItself]="true" (nearOfBottom)="onPlaylistVideosNearOfBottom()"> | 1 | <div |
2 | *ngIf="playlist && currentPlaylistPosition" class="playlist" | ||
3 | myInfiniteScroller [autoInit]="true" [onItself]="true" (nearOfBottom)="onPlaylistVideosNearOfBottom()" | ||
4 | > | ||
2 | <div class="playlist-info"> | 5 | <div class="playlist-info"> |
3 | <div class="playlist-display-name"> | 6 | <div class="playlist-display-name"> |
4 | {{ playlist.displayName }} | 7 | {{ playlist.displayName }} |
@@ -36,7 +39,7 @@ | |||
36 | </div> | 39 | </div> |
37 | </div> | 40 | </div> |
38 | 41 | ||
39 | <div *ngFor="let playlistElement of playlistElements"> | 42 | <div *ngFor="let playlistElement of playlistElements" [ngClass]="'element-' + playlistElement.position"> |
40 | <my-video-playlist-element-miniature | 43 | <my-video-playlist-element-miniature |
41 | [playlistElement]="playlistElement" [playlist]="playlist" [owned]="isPlaylistOwned()" (elementRemoved)="onElementRemoved($event)" | 44 | [playlistElement]="playlistElement" [playlist]="playlist" [owned]="isPlaylistOwned()" (elementRemoved)="onElementRemoved($event)" |
42 | [playing]="currentPlaylistPosition === playlistElement.position" [accountLink]="false" [position]="playlistElement.position" | 45 | [playing]="currentPlaylistPosition === playlistElement.position" [accountLink]="false" [position]="playlistElement.position" |
diff --git a/client/src/app/+videos/+video-watch/video-watch-playlist.component.ts b/client/src/app/+videos/+video-watch/video-watch-playlist.component.ts index c60ca4671..d76d0bbd2 100644 --- a/client/src/app/+videos/+video-watch/video-watch-playlist.component.ts +++ b/client/src/app/+videos/+video-watch/video-watch-playlist.component.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | |
2 | import { Component, EventEmitter, Input, Output } from '@angular/core' | ||
2 | import { Router } from '@angular/router' | 3 | import { Router } from '@angular/router' |
3 | import { AuthService, ComponentPagination, LocalStorageService, Notifier, SessionStorageService, UserService } from '@app/core' | 4 | import { AuthService, ComponentPagination, LocalStorageService, Notifier, SessionStorageService, UserService } from '@app/core' |
4 | import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist' | 5 | import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist' |
5 | import { peertubeLocalStorage, peertubeSessionStorage } from '@root-helpers/peertube-web-storage' | 6 | import { peertubeLocalStorage, peertubeSessionStorage } from '@root-helpers/peertube-web-storage' |
6 | import { VideoDetails, VideoPlaylistPrivacy } from '@shared/models' | 7 | import { VideoPlaylistPrivacy } from '@shared/models' |
7 | 8 | ||
8 | @Component({ | 9 | @Component({ |
9 | selector: 'my-video-watch-playlist', | 10 | selector: 'my-video-watch-playlist', |
@@ -14,9 +15,10 @@ export class VideoWatchPlaylistComponent { | |||
14 | static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'auto_play_video_playlist' | 15 | static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'auto_play_video_playlist' |
15 | static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'loop_playlist' | 16 | static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'loop_playlist' |
16 | 17 | ||
17 | @Input() video: VideoDetails | ||
18 | @Input() playlist: VideoPlaylist | 18 | @Input() playlist: VideoPlaylist |
19 | 19 | ||
20 | @Output() videoFound = new EventEmitter<string>() | ||
21 | |||
20 | playlistElements: VideoPlaylistElement[] = [] | 22 | playlistElements: VideoPlaylistElement[] = [] |
21 | playlistPagination: ComponentPagination = { | 23 | playlistPagination: ComponentPagination = { |
22 | currentPage: 1, | 24 | currentPage: 1, |
@@ -29,7 +31,8 @@ export class VideoWatchPlaylistComponent { | |||
29 | loopPlaylist: boolean | 31 | loopPlaylist: boolean |
30 | loopPlaylistSwitchText = '' | 32 | loopPlaylistSwitchText = '' |
31 | noPlaylistVideos = false | 33 | noPlaylistVideos = false |
32 | currentPlaylistPosition = 1 | 34 | |
35 | currentPlaylistPosition: number | ||
33 | 36 | ||
34 | constructor ( | 37 | constructor ( |
35 | private userService: UserService, | 38 | private userService: UserService, |
@@ -44,6 +47,7 @@ export class VideoWatchPlaylistComponent { | |||
44 | this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn() | 47 | this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn() |
45 | ? this.auth.getUser().autoPlayNextVideoPlaylist | 48 | ? this.auth.getUser().autoPlayNextVideoPlaylist |
46 | : this.localStorageService.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false' | 49 | : this.localStorageService.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false' |
50 | |||
47 | this.setAutoPlayNextVideoPlaylistSwitchText() | 51 | this.setAutoPlayNextVideoPlaylistSwitchText() |
48 | 52 | ||
49 | // defaults to false | 53 | // defaults to false |
@@ -51,12 +55,12 @@ export class VideoWatchPlaylistComponent { | |||
51 | this.setLoopPlaylistSwitchText() | 55 | this.setLoopPlaylistSwitchText() |
52 | } | 56 | } |
53 | 57 | ||
54 | onPlaylistVideosNearOfBottom () { | 58 | onPlaylistVideosNearOfBottom (position?: number) { |
55 | // Last page | 59 | // Last page |
56 | if (this.playlistPagination.totalItems <= (this.playlistPagination.currentPage * this.playlistPagination.itemsPerPage)) return | 60 | if (this.playlistPagination.totalItems <= (this.playlistPagination.currentPage * this.playlistPagination.itemsPerPage)) return |
57 | 61 | ||
58 | this.playlistPagination.currentPage += 1 | 62 | this.playlistPagination.currentPage += 1 |
59 | this.loadPlaylistElements(this.playlist,false) | 63 | this.loadPlaylistElements(this.playlist, false, position) |
60 | } | 64 | } |
61 | 65 | ||
62 | onElementRemoved (playlistElement: VideoPlaylistElement) { | 66 | onElementRemoved (playlistElement: VideoPlaylistElement) { |
@@ -83,26 +87,26 @@ export class VideoWatchPlaylistComponent { | |||
83 | return this.playlist.privacy.id === VideoPlaylistPrivacy.PUBLIC | 87 | return this.playlist.privacy.id === VideoPlaylistPrivacy.PUBLIC |
84 | } | 88 | } |
85 | 89 | ||
86 | loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false) { | 90 | loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false, position?: number) { |
87 | this.videoPlaylist.getPlaylistVideos(playlist.uuid, this.playlistPagination) | 91 | this.videoPlaylist.getPlaylistVideos(playlist.uuid, this.playlistPagination) |
88 | .subscribe(({ total, data }) => { | 92 | .subscribe(({ total, data }) => { |
89 | this.playlistElements = this.playlistElements.concat(data) | 93 | this.playlistElements = this.playlistElements.concat(data) |
90 | this.playlistPagination.totalItems = total | 94 | this.playlistPagination.totalItems = total |
91 | 95 | ||
92 | const firstAvailableVideos = this.playlistElements.find(e => !!e.video) | 96 | const firstAvailableVideo = this.playlistElements.find(e => !!e.video) |
93 | if (!firstAvailableVideos) { | 97 | if (!firstAvailableVideo) { |
94 | this.noPlaylistVideos = true | 98 | this.noPlaylistVideos = true |
95 | return | 99 | return |
96 | } | 100 | } |
97 | 101 | ||
98 | this.updatePlaylistIndex(this.video) | 102 | if (position) this.updatePlaylistIndex(position) |
99 | 103 | ||
100 | if (redirectToFirst) { | 104 | if (redirectToFirst) { |
101 | const extras = { | 105 | const extras = { |
102 | queryParams: { | 106 | queryParams: { |
103 | start: firstAvailableVideos.startTimestamp, | 107 | start: firstAvailableVideo.startTimestamp, |
104 | stop: firstAvailableVideos.stopTimestamp, | 108 | stop: firstAvailableVideo.stopTimestamp, |
105 | videoId: firstAvailableVideos.video.uuid | 109 | playlistPosition: firstAvailableVideo.position |
106 | }, | 110 | }, |
107 | replaceUrl: true | 111 | replaceUrl: true |
108 | } | 112 | } |
@@ -111,18 +115,26 @@ export class VideoWatchPlaylistComponent { | |||
111 | }) | 115 | }) |
112 | } | 116 | } |
113 | 117 | ||
114 | updatePlaylistIndex (video: VideoDetails) { | 118 | updatePlaylistIndex (position: number) { |
115 | if (this.playlistElements.length === 0 || !video) return | 119 | if (this.playlistElements.length === 0 || !position) return |
116 | 120 | ||
117 | for (const playlistElement of this.playlistElements) { | 121 | for (const playlistElement of this.playlistElements) { |
118 | if (playlistElement.video && playlistElement.video.id === video.id) { | 122 | // >= if the previous videos were not valid |
123 | if (playlistElement.video && playlistElement.position >= position) { | ||
119 | this.currentPlaylistPosition = playlistElement.position | 124 | this.currentPlaylistPosition = playlistElement.position |
125 | |||
126 | this.videoFound.emit(playlistElement.video.uuid) | ||
127 | |||
128 | setTimeout(() => { | ||
129 | document.querySelector('.element-' + this.currentPlaylistPosition).scrollIntoView(false) | ||
130 | }, 0) | ||
131 | |||
120 | return | 132 | return |
121 | } | 133 | } |
122 | } | 134 | } |
123 | 135 | ||
124 | // Load more videos to find our video | 136 | // Load more videos to find our video |
125 | this.onPlaylistVideosNearOfBottom() | 137 | this.onPlaylistVideosNearOfBottom(position) |
126 | } | 138 | } |
127 | 139 | ||
128 | findNextPlaylistVideo (position = this.currentPlaylistPosition): VideoPlaylistElement { | 140 | findNextPlaylistVideo (position = this.currentPlaylistPosition): VideoPlaylistElement { |
@@ -147,9 +159,10 @@ export class VideoWatchPlaylistComponent { | |||
147 | navigateToNextPlaylistVideo () { | 159 | navigateToNextPlaylistVideo () { |
148 | const next = this.findNextPlaylistVideo(this.currentPlaylistPosition + 1) | 160 | const next = this.findNextPlaylistVideo(this.currentPlaylistPosition + 1) |
149 | if (!next) return | 161 | if (!next) return |
162 | |||
150 | const start = next.startTimestamp | 163 | const start = next.startTimestamp |
151 | const stop = next.stopTimestamp | 164 | const stop = next.stopTimestamp |
152 | this.router.navigate([],{ queryParams: { videoId: next.video.uuid, start, stop } }) | 165 | this.router.navigate([],{ queryParams: { playlistPosition: next.position, start, stop } }) |
153 | } | 166 | } |
154 | 167 | ||
155 | switchAutoPlayNextVideoPlaylist () { | 168 | switchAutoPlayNextVideoPlaylist () { |
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.html b/client/src/app/+videos/+video-watch/video-watch.component.html index 4279437d2..076c6205f 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.html +++ b/client/src/app/+videos/+video-watch/video-watch.component.html | |||
@@ -11,7 +11,8 @@ | |||
11 | 11 | ||
12 | <my-video-watch-playlist | 12 | <my-video-watch-playlist |
13 | #videoWatchPlaylist | 13 | #videoWatchPlaylist |
14 | [video]="video" [playlist]="playlist" class="playlist" | 14 | [playlist]="playlist" class="playlist" |
15 | (videoFound)="onPlaylistVideoFound($event)" | ||
15 | ></my-video-watch-playlist> | 16 | ></my-video-watch-playlist> |
16 | </div> | 17 | </div> |
17 | 18 | ||
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts index 4df2c6c5e..7ff860e79 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.ts +++ b/client/src/app/+videos/+video-watch/video-watch.component.ts | |||
@@ -53,6 +53,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
53 | video: VideoDetails = null | 53 | video: VideoDetails = null |
54 | videoCaptions: VideoCaption[] = [] | 54 | videoCaptions: VideoCaption[] = [] |
55 | 55 | ||
56 | playlistPosition: number | ||
56 | playlist: VideoPlaylist = null | 57 | playlist: VideoPlaylist = null |
57 | 58 | ||
58 | completeDescriptionShown = false | 59 | completeDescriptionShown = false |
@@ -140,9 +141,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
140 | if (playlistId) this.loadPlaylist(playlistId) | 141 | if (playlistId) this.loadPlaylist(playlistId) |
141 | }) | 142 | }) |
142 | 143 | ||
143 | this.queryParamsSub = this.route.queryParams.subscribe(async queryParams => { | 144 | this.queryParamsSub = this.route.queryParams.subscribe(queryParams => { |
144 | const videoId = queryParams[ 'videoId' ] | 145 | this.playlistPosition = queryParams[ 'playlistPosition' ] |
145 | if (videoId) this.loadVideo(videoId) | 146 | this.videoWatchPlaylist.updatePlaylistIndex(this.playlistPosition) |
146 | 147 | ||
147 | const start = queryParams[ 'start' ] | 148 | const start = queryParams[ 'start' ] |
148 | if (this.player && start) this.player.currentTime(parseInt(start, 10)) | 149 | if (this.player && start) this.player.currentTime(parseInt(start, 10)) |
@@ -335,6 +336,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
335 | return genericChannelDisplayName.includes(this.video.channel.displayName) | 336 | return genericChannelDisplayName.includes(this.video.channel.displayName) |
336 | } | 337 | } |
337 | 338 | ||
339 | onPlaylistVideoFound (videoId: string) { | ||
340 | this.loadVideo(videoId) | ||
341 | } | ||
342 | |||
338 | private loadVideo (videoId: string) { | 343 | private loadVideo (videoId: string) { |
339 | // Video did not change | 344 | // Video did not change |
340 | if (this.video && this.video.uuid === videoId) return | 345 | if (this.video && this.video.uuid === videoId) return |
@@ -392,8 +397,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
392 | .subscribe(playlist => { | 397 | .subscribe(playlist => { |
393 | this.playlist = playlist | 398 | this.playlist = playlist |
394 | 399 | ||
395 | const videoId = this.route.snapshot.queryParams['videoId'] | 400 | this.videoWatchPlaylist.loadPlaylistElements(playlist, !this.playlistPosition, this.playlistPosition) |
396 | this.videoWatchPlaylist.loadPlaylistElements(playlist, !videoId) | ||
397 | }) | 401 | }) |
398 | } | 402 | } |
399 | 403 | ||
@@ -458,8 +462,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
458 | this.remoteServerDown = false | 462 | this.remoteServerDown = false |
459 | this.currentTime = undefined | 463 | this.currentTime = undefined |
460 | 464 | ||
461 | this.videoWatchPlaylist.updatePlaylistIndex(video) | ||
462 | |||
463 | if (this.isVideoBlur(this.video)) { | 465 | if (this.isVideoBlur(this.video)) { |
464 | const res = await this.confirmService.confirm( | 466 | const res = await this.confirmService.confirm( |
465 | $localize`This video contains mature or explicit content. Are you sure you want to watch it?`, | 467 | $localize`This video contains mature or explicit content. Are you sure you want to watch it?`, |
diff --git a/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts b/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts index f09c3d1fc..d2cf53227 100644 --- a/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts +++ b/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { fromEvent, Observable, Subscription } from 'rxjs' | ||
1 | import { distinctUntilChanged, filter, map, share, startWith, throttleTime } from 'rxjs/operators' | 2 | import { distinctUntilChanged, filter, map, share, startWith, throttleTime } from 'rxjs/operators' |
2 | import { AfterContentChecked, Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core' | 3 | import { AfterContentChecked, Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core' |
3 | import { fromEvent, Observable, Subscription } from 'rxjs' | ||
4 | 4 | ||
5 | @Directive({ | 5 | @Directive({ |
6 | selector: '[myInfiniteScroller]' | 6 | selector: '[myInfiniteScroller]' |
@@ -80,7 +80,9 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy, AfterConten | |||
80 | } | 80 | } |
81 | 81 | ||
82 | private getMaximumScroll () { | 82 | private getMaximumScroll () { |
83 | return this.container.scrollHeight - window.innerHeight | 83 | const elementHeight = this.onItself ? this.container.clientHeight : window.innerHeight |
84 | |||
85 | return this.container.scrollHeight - elementHeight | ||
84 | } | 86 | } |
85 | 87 | ||
86 | private hasScroll () { | 88 | private hasScroll () { |
diff --git a/client/src/app/shared/shared-share-modal/video-share.component.ts b/client/src/app/shared/shared-share-modal/video-share.component.ts index 8d8e8a3a5..f57a50770 100644 --- a/client/src/app/shared/shared-share-modal/video-share.component.ts +++ b/client/src/app/shared/shared-share-modal/video-share.component.ts | |||
@@ -37,6 +37,7 @@ export class VideoShareComponent { | |||
37 | @Input() video: VideoDetails = null | 37 | @Input() video: VideoDetails = null |
38 | @Input() videoCaptions: VideoCaption[] = [] | 38 | @Input() videoCaptions: VideoCaption[] = [] |
39 | @Input() playlist: VideoPlaylist = null | 39 | @Input() playlist: VideoPlaylist = null |
40 | @Input() playlistPosition: number = null | ||
40 | 41 | ||
41 | activeVideoId: TabId = 'url' | 42 | activeVideoId: TabId = 'url' |
42 | activePlaylistId: TabId = 'url' | 43 | activePlaylistId: TabId = 'url' |
@@ -45,8 +46,6 @@ export class VideoShareComponent { | |||
45 | isAdvancedCustomizationCollapsed = true | 46 | isAdvancedCustomizationCollapsed = true |
46 | includeVideoInPlaylist = false | 47 | includeVideoInPlaylist = false |
47 | 48 | ||
48 | private playlistPosition: number = null | ||
49 | |||
50 | constructor (private modalService: NgbModal) { } | 49 | constructor (private modalService: NgbModal) { } |
51 | 50 | ||
52 | show (currentVideoTimestamp?: number, currentPlaylistPosition?: number) { | 51 | show (currentVideoTimestamp?: number, currentPlaylistPosition?: number) { |
@@ -107,7 +106,7 @@ export class VideoShareComponent { | |||
107 | 106 | ||
108 | if (!this.includeVideoInPlaylist) return base | 107 | if (!this.includeVideoInPlaylist) return base |
109 | 108 | ||
110 | return base + '?videoId=' + this.video.uuid | 109 | return base + '?playlistPosition=' + this.playlistPosition |
111 | } | 110 | } |
112 | 111 | ||
113 | notSecure () { | 112 | notSecure () { |
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts index 5879c4978..7c083ae26 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts | |||
@@ -78,7 +78,7 @@ export class VideoPlaylistElementMiniatureComponent implements OnInit { | |||
78 | if (!this.playlistElement || !this.playlistElement.video) return {} | 78 | if (!this.playlistElement || !this.playlistElement.video) return {} |
79 | 79 | ||
80 | return { | 80 | return { |
81 | videoId: this.playlistElement.video.uuid, | 81 | playlistPosition: this.playlistElement.position, |
82 | start: this.playlistElement.startTimestamp, | 82 | start: this.playlistElement.startTimestamp, |
83 | stop: this.playlistElement.stopTimestamp, | 83 | stop: this.playlistElement.stopTimestamp, |
84 | resume: true | 84 | resume: true |