aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss14
-rw-r--r--client/src/app/shared/video/video-actions-dropdown.component.ts8
-rw-r--r--client/src/app/videos/+video-watch/video-watch-playlist.component.html25
-rw-r--r--client/src/app/videos/+video-watch/video-watch-playlist.component.scss59
-rw-r--r--client/src/app/videos/+video-watch/video-watch-playlist.component.ts111
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html29
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.scss66
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts106
-rw-r--r--client/src/app/videos/+video-watch/video-watch.module.ts2
9 files changed, 227 insertions, 193 deletions
diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss
index f8a068cbc..cb7072d7f 100644
--- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss
+++ b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss
@@ -2,6 +2,13 @@
2@import '_mixins'; 2@import '_mixins';
3@import '_miniature'; 3@import '_miniature';
4 4
5my-video-thumbnail {
6 @include thumbnail-size-component(130px, 72px);
7
8 display: flex; // Avoids an issue with line-height that adds space below the element
9 margin-right: 10px;
10}
11
5.video { 12.video {
6 display: flex; 13 display: flex;
7 align-items: center; 14 align-items: center;
@@ -44,13 +51,6 @@
44 } 51 }
45 } 52 }
46 53
47 my-video-thumbnail {
48 @include thumbnail-size-component(130px, 72px);
49
50 display: flex; // Avoids an issue with line-height that adds space below the element
51 margin-right: 10px;
52 }
53
54 .video-info { 54 .video-info {
55 display: flex; 55 display: flex;
56 flex-direction: column; 56 flex-direction: column;
diff --git a/client/src/app/shared/video/video-actions-dropdown.component.ts b/client/src/app/shared/video/video-actions-dropdown.component.ts
index b2d77a9e6..c1da0eba6 100644
--- a/client/src/app/shared/video/video-actions-dropdown.component.ts
+++ b/client/src/app/shared/video/video-actions-dropdown.component.ts
@@ -1,4 +1,4 @@
1import { AfterContentInit, AfterViewInit, Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core' 1import { AfterViewInit, Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core'
2import { I18n } from '@ngx-translate/i18n-polyfill' 2import { I18n } from '@ngx-translate/i18n-polyfill'
3import { DropdownAction, DropdownButtonSize, DropdownDirection } from '@app/shared/buttons/action-dropdown.component' 3import { DropdownAction, DropdownButtonSize, DropdownDirection } from '@app/shared/buttons/action-dropdown.component'
4import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' 4import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core'
@@ -133,6 +133,10 @@ export class VideoActionsDropdownComponent implements AfterViewInit, OnChanges {
133 return this.video.isUnblacklistableBy(this.user) 133 return this.video.isUnblacklistableBy(this.user)
134 } 134 }
135 135
136 isVideoDownloadable () {
137 return this.video && this.video instanceof VideoDetails && this.video.downloadEnabled
138 }
139
136 /* Action handlers */ 140 /* Action handlers */
137 141
138 async unblacklistVideo () { 142 async unblacklistVideo () {
@@ -202,7 +206,7 @@ export class VideoActionsDropdownComponent implements AfterViewInit, OnChanges {
202 { 206 {
203 label: this.i18n('Download'), 207 label: this.i18n('Download'),
204 handler: () => this.showDownloadModal(), 208 handler: () => this.showDownloadModal(),
205 isDisplayed: () => this.displayOptions.download, 209 isDisplayed: () => this.displayOptions.download && this.isVideoDownloadable(),
206 iconName: 'download' 210 iconName: 'download'
207 }, 211 },
208 { 212 {
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
new file mode 100644
index 000000000..c168a3130
--- /dev/null
+++ b/client/src/app/videos/+video-watch/video-watch-playlist.component.html
@@ -0,0 +1,25 @@
1<div *ngIf="playlist && video" class="playlist" myInfiniteScroller [autoInit]="true" [onItself]="true" (nearOfBottom)="onPlaylistVideosNearOfBottom()">
2 <div class="playlist-info">
3 <div class="playlist-display-name">
4 {{ playlist.displayName }}
5
6 <span *ngIf="isUnlistedPlaylist()" class="badge badge-warning" i18n>Unlisted</span>
7 <span *ngIf="isPrivatePlaylist()" class="badge badge-danger" i18n>Private</span>
8 <span *ngIf="isPublicPlaylist()" class="badge badge-info" i18n>Public</span>
9 </div>
10
11 <div class="playlist-by-index">
12 <div class="playlist-by">{{ playlist.ownerBy }}</div>
13 <div class="playlist-index">
14 <span>{{ currentPlaylistPosition }}</span><span>{{ playlistPagination.totalItems }}</span>
15 </div>
16 </div>
17 </div>
18
19 <div *ngFor="let playlistVideo of playlistVideos">
20 <my-video-playlist-element-miniature
21 [video]="playlistVideo" [playlist]="playlist" [owned]="isPlaylistOwned()" (elementRemoved)="onElementRemoved($event)"
22 [playing]="currentPlaylistPosition === playlistVideo.playlistElement.position" [accountLink]="false" [position]="playlistVideo.playlistElement.position"
23 ></my-video-playlist-element-miniature>
24 </div>
25</div>
diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.scss b/client/src/app/videos/+video-watch/video-watch-playlist.component.scss
new file mode 100644
index 000000000..5da55c2f8
--- /dev/null
+++ b/client/src/app/videos/+video-watch/video-watch-playlist.component.scss
@@ -0,0 +1,59 @@
1@import '_variables';
2@import '_mixins';
3@import '_bootstrap-variables';
4@import '_miniature';
5
6.playlist {
7 min-width: 200px;
8 max-width: 470px;
9 height: 66vh;
10 background-color: var(--mainBackgroundColor);
11 overflow-y: auto;
12 border-bottom: 1px solid $separator-border-color;
13
14 .playlist-info {
15 padding: 5px 30px;
16 background-color: #e4e4e4;
17
18 .playlist-display-name {
19 font-size: 18px;
20 font-weight: $font-semibold;
21 margin-bottom: 5px;
22 }
23
24 .playlist-by-index {
25 color: $grey-foreground-color;
26 display: flex;
27
28 .playlist-by {
29 margin-right: 5px;
30 }
31
32 .playlist-index span:first-child::after {
33 content: '/';
34 margin: 0 3px;
35 }
36 }
37 }
38
39 my-video-playlist-element-miniature {
40 /deep/ {
41 .video {
42 .position {
43 margin-right: 0;
44 }
45
46 .video-info {
47 .video-info-name {
48 font-size: 15px;
49 }
50 }
51 }
52
53 my-video-thumbnail {
54 @include thumbnail-size-component(90px, 50px);
55 }
56 }
57 }
58}
59
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
new file mode 100644
index 000000000..3ac06c099
--- /dev/null
+++ b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts
@@ -0,0 +1,111 @@
1import { Component, Input } from '@angular/core'
2import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
3import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
4import { Video } from '@app/shared/video/video.model'
5import { VideoDetails, VideoPlaylistPrivacy } from '@shared/models'
6import { VideoService } from '@app/shared/video/video.service'
7import { Router } from '@angular/router'
8import { AuthService } from '@app/core'
9
10@Component({
11 selector: 'my-video-watch-playlist',
12 templateUrl: './video-watch-playlist.component.html',
13 styleUrls: [ './video-watch-playlist.component.scss' ]
14})
15export class VideoWatchPlaylistComponent {
16 @Input() video: VideoDetails
17 @Input() playlist: VideoPlaylist
18
19 playlistVideos: Video[] = []
20 playlistPagination: ComponentPagination = {
21 currentPage: 1,
22 itemsPerPage: 30,
23 totalItems: null
24 }
25
26 noPlaylistVideos = false
27 currentPlaylistPosition = 1
28
29 constructor (
30 private auth: AuthService,
31 private videoService: VideoService,
32 private router: Router
33 ) {}
34
35 onPlaylistVideosNearOfBottom () {
36 // Last page
37 if (this.playlistPagination.totalItems <= (this.playlistPagination.currentPage * this.playlistPagination.itemsPerPage)) return
38
39 this.playlistPagination.currentPage += 1
40 this.loadPlaylistElements(this.playlist,false)
41 }
42
43 onElementRemoved (video: Video) {
44 this.playlistVideos = this.playlistVideos.filter(v => v.id !== video.id)
45
46 this.playlistPagination.totalItems--
47 }
48
49 isPlaylistOwned () {
50 return this.playlist.isLocal === true && this.playlist.ownerAccount.name === this.auth.getUser().username
51 }
52
53 isUnlistedPlaylist () {
54 return this.playlist.privacy.id === VideoPlaylistPrivacy.UNLISTED
55 }
56
57 isPrivatePlaylist () {
58 return this.playlist.privacy.id === VideoPlaylistPrivacy.PRIVATE
59 }
60
61 isPublicPlaylist () {
62 return this.playlist.privacy.id === VideoPlaylistPrivacy.PUBLIC
63 }
64
65 loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false) {
66 this.videoService.getPlaylistVideos(playlist.uuid, this.playlistPagination)
67 .subscribe(({ totalVideos, videos }) => {
68 this.playlistVideos = this.playlistVideos.concat(videos)
69 this.playlistPagination.totalItems = totalVideos
70
71 if (totalVideos === 0) {
72 this.noPlaylistVideos = true
73 return
74 }
75
76 this.updatePlaylistIndex(this.video)
77
78 if (redirectToFirst) {
79 const extras = {
80 queryParams: { videoId: this.playlistVideos[ 0 ].uuid },
81 replaceUrl: true
82 }
83 this.router.navigate([], extras)
84 }
85 })
86 }
87
88 updatePlaylistIndex (video: VideoDetails) {
89 if (this.playlistVideos.length === 0 || !video) return
90
91 for (const playlistVideo of this.playlistVideos) {
92 if (playlistVideo.id === video.id) {
93 this.currentPlaylistPosition = playlistVideo.playlistElement.position
94 return
95 }
96 }
97
98 // Load more videos to find our video
99 this.onPlaylistVideosNearOfBottom()
100 }
101
102 navigateToNextPlaylistVideo () {
103 if (this.currentPlaylistPosition < this.playlistPagination.totalItems) {
104 const next = this.playlistVideos.find(v => v.playlistElement.position === this.currentPlaylistPosition + 1)
105
106 const start = next.playlistElement.startTimestamp
107 const stop = next.playlistElement.stopTimestamp
108 this.router.navigate([],{ queryParams: { videoId: next.uuid, start, stop } })
109 }
110 }
111}
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 7e9b89dd0..7da74b57e 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -9,31 +9,10 @@
9 9
10 <div id="videojs-wrapper"></div> 10 <div id="videojs-wrapper"></div>
11 11
12 <div *ngIf="playlist && video" class="playlist" myInfiniteScroller [autoInit]="true" [onItself]="true" (nearOfBottom)="onPlaylistVideosNearOfBottom()"> 12 <my-video-watch-playlist
13 <div class="playlist-info"> 13 #videoWatchPlaylist
14 <div class="playlist-display-name"> 14 [video]="video" [playlist]="playlist" class="playlist"
15 {{ playlist.displayName }} 15 ></my-video-watch-playlist>
16
17 <span *ngIf="isUnlistedPlaylist()" class="badge badge-warning" i18n>Unlisted</span>
18 <span *ngIf="isPrivatePlaylist()" class="badge badge-danger" i18n>Private</span>
19 <span *ngIf="isPublicPlaylist()" class="badge badge-info" i18n>Public</span>
20 </div>
21
22 <div class="playlist-by-index">
23 <div class="playlist-by">{{ playlist.ownerBy }}</div>
24 <div class="playlist-index">
25 <span>{{ currentPlaylistPosition }}</span><span>{{ playlistPagination.totalItems }}</span>
26 </div>
27 </div>
28 </div>
29
30 <div *ngFor="let playlistVideo of playlistVideos">
31 <my-video-playlist-element-miniature
32 [video]="playlistVideo" [playlist]="playlist" [owned]="isPlaylistOwned()" (elementRemoved)="onElementRemoved($event)"
33 [playing]="currentPlaylistPosition === playlistVideo.playlistElement.position" [accountLink]="false" [position]="playlistVideo.playlistElement.position"
34 ></my-video-playlist-element-miniature>
35 </div>
36 </div>
37 </div> 16 </div>
38 17
39 <div class="row"> 18 <div class="row">
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index d8113b666..8ca5c4118 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -15,10 +15,10 @@ $player-factor: 1.7; // 16/9
15} 15}
16 16
17@mixin playlist-below-player { 17@mixin playlist-below-player {
18 width: 100%; 18 width: 100% !important;
19 height: auto; 19 height: auto !important;
20 max-height: 300px; 20 max-height: 300px !important;
21 border-bottom: 1px solid $separator-border-color; 21 border-bottom: 1px solid $separator-border-color !important;
22} 22}
23 23
24.root { 24.root {
@@ -37,7 +37,7 @@ $player-factor: 1.7; // 16/9
37 width: 100%; 37 width: 100%;
38 } 38 }
39 39
40 .playlist { 40 my-video-watch-playlist /deep/ .playlist {
41 @include playlist-below-player; 41 @include playlist-below-player;
42 } 42 }
43 } 43 }
@@ -80,60 +80,6 @@ $player-factor: 1.7; // 16/9
80 } 80 }
81 } 81 }
82 82
83 .playlist {
84 min-width: 200px;
85 max-width: 470px;
86 height: 66vh;
87 background-color: var(--mainBackgroundColor);
88 overflow-y: auto;
89 border-bottom: 1px solid $separator-border-color;
90
91 .playlist-info {
92 padding: 5px 30px;
93 background-color: #e4e4e4;
94
95 .playlist-display-name {
96 font-size: 18px;
97 font-weight: $font-semibold;
98 margin-bottom: 5px;
99 }
100
101 .playlist-by-index {
102 color: $grey-foreground-color;
103 display: flex;
104
105 .playlist-by {
106 margin-right: 5px;
107 }
108
109 .playlist-index span:first-child::after {
110 content: '/';
111 margin: 0 3px;
112 }
113 }
114 }
115
116 my-video-playlist-element-miniature {
117 /deep/ {
118 .video {
119 .position {
120 margin-right: 0;
121 }
122
123 .video-info {
124 .video-info-name {
125 font-size: 15px;
126 }
127 }
128 }
129
130 my-video-thumbnail {
131 @include thumbnail-size-component(90px, 50px);
132 }
133 }
134 }
135 }
136
137 /deep/ .video-js { 83 /deep/ .video-js {
138 width: getPlayerWidth(66vh); 84 width: getPlayerWidth(66vh);
139 height: 66vh; 85 height: 66vh;
@@ -508,7 +454,7 @@ my-video-comments {
508 flex-direction: column; 454 flex-direction: column;
509 justify-content: center; 455 justify-content: center;
510 456
511 .playlist { 457 my-video-watch-playlist /deep/ .playlist {
512 @include playlist-below-player; 458 @include playlist-below-player;
513 } 459 }
514 } 460 }
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 bce652210..0532e7de7 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -8,7 +8,7 @@ import { MetaService } from '@ngx-meta/core'
8import { Notifier, ServerService } from '@app/core' 8import { Notifier, ServerService } from '@app/core'
9import { forkJoin, Subscription } from 'rxjs' 9import { forkJoin, Subscription } from 'rxjs'
10import { Hotkey, HotkeysService } from 'angular2-hotkeys' 10import { Hotkey, HotkeysService } from 'angular2-hotkeys'
11import { UserVideoRateType, VideoCaption, VideoPlaylistPrivacy, VideoPrivacy, VideoState } from '../../../../../shared' 11import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared'
12import { AuthService, ConfirmService } from '../../core' 12import { AuthService, ConfirmService } from '../../core'
13import { RestExtractor, VideoBlacklistService } from '../../shared' 13import { RestExtractor, VideoBlacklistService } from '../../shared'
14import { VideoDetails } from '../../shared/video/video-details.model' 14import { VideoDetails } from '../../shared/video/video-details.model'
@@ -27,9 +27,9 @@ import {
27} from '../../../assets/player/peertube-player-manager' 27} from '../../../assets/player/peertube-player-manager'
28import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' 28import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
29import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' 29import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
30import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
31import { Video } from '@app/shared/video/video.model' 30import { Video } from '@app/shared/video/video.model'
32import { isWebRTCDisabled } from '../../../assets/player/utils' 31import { isWebRTCDisabled } from '../../../assets/player/utils'
32import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component'
33 33
34@Component({ 34@Component({
35 selector: 'my-video-watch', 35 selector: 'my-video-watch',
@@ -39,6 +39,7 @@ import { isWebRTCDisabled } from '../../../assets/player/utils'
39export class VideoWatchComponent implements OnInit, OnDestroy { 39export class VideoWatchComponent implements OnInit, OnDestroy {
40 private static LOCAL_STORAGE_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern' 40 private static LOCAL_STORAGE_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern'
41 41
42 @ViewChild('videoWatchPlaylist') videoWatchPlaylist: VideoWatchPlaylistComponent
42 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent 43 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
43 @ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent 44 @ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent
44 @ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent 45 @ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent
@@ -51,14 +52,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
51 descriptionLoading = false 52 descriptionLoading = false
52 53
53 playlist: VideoPlaylist = null 54 playlist: VideoPlaylist = null
54 playlistVideos: Video[] = []
55 playlistPagination: ComponentPagination = {
56 currentPage: 1,
57 itemsPerPage: 30,
58 totalItems: null
59 }
60 noPlaylistVideos = false
61 currentPlaylistPosition = 1
62 55
63 completeDescriptionShown = false 56 completeDescriptionShown = false
64 completeVideoDescription: string 57 completeVideoDescription: string
@@ -230,10 +223,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
230 return this.video.tags 223 return this.video.tags
231 } 224 }
232 225
233 isVideoRemovable () {
234 return this.video.isRemovableBy(this.authService.getUser())
235 }
236
237 onVideoRemoved () { 226 onVideoRemoved () {
238 this.redirectService.redirectToHomepage() 227 this.redirectService.redirectToHomepage()
239 } 228 }
@@ -247,10 +236,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
247 return this.video && this.video.state.id === VideoState.TO_TRANSCODE 236 return this.video && this.video.state.id === VideoState.TO_TRANSCODE
248 } 237 }
249 238
250 isVideoDownloadable () {
251 return this.video && this.video.downloadEnabled
252 }
253
254 isVideoToImport () { 239 isVideoToImport () {
255 return this.video && this.video.state.id === VideoState.TO_IMPORT 240 return this.video && this.video.state.id === VideoState.TO_IMPORT
256 } 241 }
@@ -263,36 +248,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
263 return video.isVideoNSFWForUser(this.user, this.serverService.getConfig()) 248 return video.isVideoNSFWForUser(this.user, this.serverService.getConfig())
264 } 249 }
265 250
266 isPlaylistOwned () {
267 return this.playlist.isLocal === true && this.playlist.ownerAccount.name === this.user.username
268 }
269
270 isUnlistedPlaylist () {
271 return this.playlist.privacy.id === VideoPlaylistPrivacy.UNLISTED
272 }
273
274 isPrivatePlaylist () {
275 return this.playlist.privacy.id === VideoPlaylistPrivacy.PRIVATE
276 }
277
278 isPublicPlaylist () {
279 return this.playlist.privacy.id === VideoPlaylistPrivacy.PUBLIC
280 }
281
282 onPlaylistVideosNearOfBottom () {
283 // Last page
284 if (this.playlistPagination.totalItems <= (this.playlistPagination.currentPage * this.playlistPagination.itemsPerPage)) return
285
286 this.playlistPagination.currentPage += 1
287 this.loadPlaylistElements(false)
288 }
289
290 onElementRemoved (video: Video) {
291 this.playlistVideos = this.playlistVideos.filter(v => v.id !== video.id)
292
293 this.playlistPagination.totalItems--
294 }
295
296 private loadVideo (videoId: string) { 251 private loadVideo (videoId: string) {
297 // Video did not change 252 // Video did not change
298 if (this.video && this.video.uuid === videoId) return 253 if (this.video && this.video.uuid === videoId) return
@@ -333,33 +288,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
333 this.playlist = playlist 288 this.playlist = playlist
334 289
335 const videoId = this.route.snapshot.queryParams['videoId'] 290 const videoId = this.route.snapshot.queryParams['videoId']
336 this.loadPlaylistElements(!videoId) 291 this.videoWatchPlaylist.loadPlaylistElements(playlist, !videoId)
337 }) 292 })
338 } 293 }
339 294
340 private loadPlaylistElements (redirectToFirst = false) {
341 this.videoService.getPlaylistVideos(this.playlist.uuid, this.playlistPagination)
342 .subscribe(({ totalVideos, videos }) => {
343 this.playlistVideos = this.playlistVideos.concat(videos)
344 this.playlistPagination.totalItems = totalVideos
345
346 if (totalVideos === 0) {
347 this.noPlaylistVideos = true
348 return
349 }
350
351 this.updatePlaylistIndex()
352
353 if (redirectToFirst) {
354 const extras = {
355 queryParams: { videoId: this.playlistVideos[ 0 ].uuid },
356 replaceUrl: true
357 }
358 this.router.navigate([], extras)
359 }
360 })
361 }
362
363 private updateVideoDescription (description: string) { 295 private updateVideoDescription (description: string) {
364 this.video.description = description 296 this.video.description = description
365 this.setVideoDescriptionHTML() 297 this.setVideoDescriptionHTML()
@@ -421,7 +353,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
421 this.remoteServerDown = false 353 this.remoteServerDown = false
422 this.currentTime = undefined 354 this.currentTime = undefined
423 355
424 this.updatePlaylistIndex() 356 this.videoWatchPlaylist.updatePlaylistIndex(video)
425 357
426 let startTime = urlOptions.startTime || (this.video.userHistory ? this.video.userHistory.currentTime : 0) 358 let startTime = urlOptions.startTime || (this.video.userHistory ? this.video.userHistory.currentTime : 0)
427 // If we are at the end of the video, reset the timer 359 // If we are at the end of the video, reset the timer
@@ -519,13 +451,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
519 451
520 this.player.one('ended', () => { 452 this.player.one('ended', () => {
521 if (this.playlist) { 453 if (this.playlist) {
522 this.zone.run(() => this.navigateToNextPlaylistVideo()) 454 this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
523 } 455 }
524 }) 456 })
525 457
526 this.player.one('stopped', () => { 458 this.player.one('stopped', () => {
527 if (this.playlist) { 459 if (this.playlist) {
528 this.zone.run(() => this.navigateToNextPlaylistVideo()) 460 this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
529 } 461 }
530 }) 462 })
531 463
@@ -586,20 +518,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
586 this.setVideoLikesBarTooltipText() 518 this.setVideoLikesBarTooltipText()
587 } 519 }
588 520
589 private updatePlaylistIndex () {
590 if (this.playlistVideos.length === 0 || !this.video) return
591
592 for (const video of this.playlistVideos) {
593 if (video.id === this.video.id) {
594 this.currentPlaylistPosition = video.playlistElement.position
595 return
596 }
597 }
598
599 // Load more videos to find our video
600 this.onPlaylistVideosNearOfBottom()
601 }
602
603 private setOpenGraphTags () { 521 private setOpenGraphTags () {
604 this.metaService.setTitle(this.video.name) 522 this.metaService.setTitle(this.video.name)
605 523
@@ -639,14 +557,4 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
639 this.player = undefined 557 this.player = undefined
640 } 558 }
641 } 559 }
642
643 private navigateToNextPlaylistVideo () {
644 if (this.currentPlaylistPosition < this.playlistPagination.totalItems) {
645 const next = this.playlistVideos.find(v => v.playlistElement.position === this.currentPlaylistPosition + 1)
646
647 const start = next.playlistElement.startTimestamp
648 const stop = next.playlistElement.stopTimestamp
649 this.router.navigate([],{ queryParams: { videoId: next.uuid, start, stop } })
650 }
651 }
652} 560}
diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts
index 983350f52..67596a3da 100644
--- a/client/src/app/videos/+video-watch/video-watch.module.ts
+++ b/client/src/app/videos/+video-watch/video-watch.module.ts
@@ -11,6 +11,7 @@ import { VideoWatchComponent } from './video-watch.component'
11import { NgxQRCodeModule } from 'ngx-qrcode2' 11import { NgxQRCodeModule } from 'ngx-qrcode2'
12import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' 12import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
13import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' 13import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module'
14import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component'
14 15
15@NgModule({ 16@NgModule({
16 imports: [ 17 imports: [
@@ -23,6 +24,7 @@ import { RecommendationsModule } from '@app/videos/recommendations/recommendatio
23 24
24 declarations: [ 25 declarations: [
25 VideoWatchComponent, 26 VideoWatchComponent,
27 VideoWatchPlaylistComponent,
26 28
27 VideoShareComponent, 29 VideoShareComponent,
28 VideoSupportComponent, 30 VideoSupportComponent,