diff options
Diffstat (limited to 'client')
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 | ||
5 | my-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 @@ | |||
1 | import { AfterContentInit, AfterViewInit, Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { AfterViewInit, Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' |
2 | import { I18n } from '@ngx-translate/i18n-polyfill' | 2 | import { I18n } from '@ngx-translate/i18n-polyfill' |
3 | import { DropdownAction, DropdownButtonSize, DropdownDirection } from '@app/shared/buttons/action-dropdown.component' | 3 | import { DropdownAction, DropdownButtonSize, DropdownDirection } from '@app/shared/buttons/action-dropdown.component' |
4 | import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' | 4 | import { 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 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | ||
3 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | ||
4 | import { Video } from '@app/shared/video/video.model' | ||
5 | import { VideoDetails, VideoPlaylistPrivacy } from '@shared/models' | ||
6 | import { VideoService } from '@app/shared/video/video.service' | ||
7 | import { Router } from '@angular/router' | ||
8 | import { 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 | }) | ||
15 | export 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' | |||
8 | import { Notifier, ServerService } from '@app/core' | 8 | import { Notifier, ServerService } from '@app/core' |
9 | import { forkJoin, Subscription } from 'rxjs' | 9 | import { forkJoin, Subscription } from 'rxjs' |
10 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 10 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
11 | import { UserVideoRateType, VideoCaption, VideoPlaylistPrivacy, VideoPrivacy, VideoState } from '../../../../../shared' | 11 | import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' |
12 | import { AuthService, ConfirmService } from '../../core' | 12 | import { AuthService, ConfirmService } from '../../core' |
13 | import { RestExtractor, VideoBlacklistService } from '../../shared' | 13 | import { RestExtractor, VideoBlacklistService } from '../../shared' |
14 | import { VideoDetails } from '../../shared/video/video-details.model' | 14 | import { 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' |
28 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | 28 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' |
29 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' | 29 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' |
30 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | ||
31 | import { Video } from '@app/shared/video/video.model' | 30 | import { Video } from '@app/shared/video/video.model' |
32 | import { isWebRTCDisabled } from '../../../assets/player/utils' | 31 | import { isWebRTCDisabled } from '../../../assets/player/utils' |
32 | import { 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' | |||
39 | export class VideoWatchComponent implements OnInit, OnDestroy { | 39 | export 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' | |||
11 | import { NgxQRCodeModule } from 'ngx-qrcode2' | 11 | import { NgxQRCodeModule } from 'ngx-qrcode2' |
12 | import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' | 12 | import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' |
13 | import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' | 13 | import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' |
14 | import { 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, |