aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+videos/+video-watch/shared/playlist
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-06-29 17:18:30 +0200
committerChocobozzz <me@florianbigard.com>2021-06-29 17:18:39 +0200
commit911186dae411d78788ccede093c251303187589a (patch)
tree967a07cd985ae4e2ea5249855726455fe929471d /client/src/app/+videos/+video-watch/shared/playlist
parentb0c43e36dbdc2c964f6828a78b146faebfb75b21 (diff)
downloadPeerTube-911186dae411d78788ccede093c251303187589a.tar.gz
PeerTube-911186dae411d78788ccede093c251303187589a.tar.zst
PeerTube-911186dae411d78788ccede093c251303187589a.zip
Reorganize watch components
Diffstat (limited to 'client/src/app/+videos/+video-watch/shared/playlist')
-rw-r--r--client/src/app/+videos/+video-watch/shared/playlist/index.ts1
-rw-r--r--client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.html49
-rw-r--r--client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.scss83
-rw-r--r--client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts229
4 files changed, 362 insertions, 0 deletions
diff --git a/client/src/app/+videos/+video-watch/shared/playlist/index.ts b/client/src/app/+videos/+video-watch/shared/playlist/index.ts
new file mode 100644
index 000000000..539705508
--- /dev/null
+++ b/client/src/app/+videos/+video-watch/shared/playlist/index.ts
@@ -0,0 +1 @@
export * from './video-watch-playlist.component'
diff --git a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.html b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.html
new file mode 100644
index 000000000..c270142a3
--- /dev/null
+++ b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.html
@@ -0,0 +1,49 @@
1<div
2 *ngIf="playlist && currentPlaylistPosition" class="playlist"
3 myInfiniteScroller [autoInit]="true" [onItself]="true" (nearOfBottom)="onPlaylistVideosNearOfBottom()"
4>
5 <div class="playlist-info">
6 <div class="playlist-display-name">
7 {{ playlist.displayName }}
8
9 <span *ngIf="isUnlistedPlaylist()" class="badge badge-warning" i18n>Unlisted</span>
10 <span *ngIf="isPrivatePlaylist()" class="badge badge-danger" i18n>Private</span>
11 <span *ngIf="isPublicPlaylist()" class="badge badge-info" i18n>Public</span>
12 </div>
13
14 <div class="playlist-by-index">
15 <div class="playlist-by">{{ playlist.ownerBy }}</div>
16 <div class="playlist-index">
17 <span>{{ currentPlaylistPosition }}</span><span>{{ playlistPagination.totalItems }}</span>
18 </div>
19 </div>
20
21 <div class="playlist-controls">
22 <my-global-icon
23 iconName="videos"
24 [class.active]="autoPlayNextVideoPlaylist"
25 (click)="switchAutoPlayNextVideoPlaylist()"
26 [ngbTooltip]="autoPlayNextVideoPlaylistSwitchText"
27 placement="bottom auto"
28 container="body"
29 ></my-global-icon>
30
31 <my-global-icon
32 iconName="repeat"
33 [class.active]="loopPlaylist"
34 (click)="switchLoopPlaylist()"
35 [ngbTooltip]="loopPlaylistSwitchText"
36 placement="bottom auto"
37 container="body"
38 ></my-global-icon>
39 </div>
40 </div>
41
42 <div *ngFor="let playlistElement of playlistElements" [ngClass]="'element-' + playlistElement.position">
43 <my-video-playlist-element-miniature
44 [playlistElement]="playlistElement" [playlist]="playlist" [owned]="isPlaylistOwned()" (elementRemoved)="onElementRemoved($event)"
45 [playing]="currentPlaylistPosition === playlistElement.position" [accountLink]="false" [position]="playlistElement.position"
46 [touchScreenEditButton]="true"
47 ></my-video-playlist-element-miniature>
48 </div>
49</div>
diff --git a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.scss b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.scss
new file mode 100644
index 000000000..75ed9d901
--- /dev/null
+++ b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.scss
@@ -0,0 +1,83 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3@use '_bootstrap-variables';
4@use '_miniature' as *;
5
6.playlist {
7 min-width: 200px;
8 max-width: 470px;
9 height: 66vh;
10 background-color: pvar(--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: pvar(--greyForegroundColor);
26 display: flex;
27
28 .playlist-by {
29 @include margin-right(5px);
30 }
31
32 .playlist-index span:first-child::after {
33 content: '/';
34 margin: 0 3px;
35 }
36 }
37
38 .playlist-controls {
39 display: flex;
40 margin: 10px 0;
41
42 my-global-icon:not(:last-child) {
43 @include margin-right(.5rem);
44 }
45
46 my-global-icon {
47 &:not(.active) {
48 opacity: .5;
49 }
50
51 ::ng-deep {
52 cursor: pointer;
53 }
54 }
55 }
56 }
57
58 my-video-playlist-element-miniature {
59 ::ng-deep {
60 .video {
61 .position {
62 @include margin-right(0);
63 }
64
65 .video-info {
66 .video-info-name {
67 font-size: 15px;
68 }
69 }
70 }
71
72 my-video-thumbnail {
73 @include thumbnail-size-component(90px, 50px);
74 }
75
76 .fake-thumbnail {
77 width: 90px;
78 height: 50px;
79 }
80 }
81 }
82}
83
diff --git a/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts
new file mode 100644
index 000000000..0a4d6bfd1
--- /dev/null
+++ b/client/src/app/+videos/+video-watch/shared/playlist/video-watch-playlist.component.ts
@@ -0,0 +1,229 @@
1
2import { Component, EventEmitter, Input, Output } from '@angular/core'
3import { Router } from '@angular/router'
4import { AuthService, ComponentPagination, LocalStorageService, Notifier, SessionStorageService, UserService } from '@app/core'
5import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist'
6import { peertubeLocalStorage, peertubeSessionStorage } from '@root-helpers/peertube-web-storage'
7import { VideoPlaylistPrivacy } from '@shared/models'
8
9@Component({
10 selector: 'my-video-watch-playlist',
11 templateUrl: './video-watch-playlist.component.html',
12 styleUrls: [ './video-watch-playlist.component.scss' ]
13})
14export class VideoWatchPlaylistComponent {
15 static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'auto_play_video_playlist'
16 static SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'loop_playlist'
17
18 @Input() playlist: VideoPlaylist
19
20 @Output() videoFound = new EventEmitter<string>()
21
22 playlistElements: VideoPlaylistElement[] = []
23 playlistPagination: ComponentPagination = {
24 currentPage: 1,
25 itemsPerPage: 30,
26 totalItems: null
27 }
28
29 autoPlayNextVideoPlaylist: boolean
30 autoPlayNextVideoPlaylistSwitchText = ''
31 loopPlaylist: boolean
32 loopPlaylistSwitchText = ''
33 noPlaylistVideos = false
34
35 currentPlaylistPosition: number
36
37 constructor (
38 private userService: UserService,
39 private auth: AuthService,
40 private notifier: Notifier,
41 private videoPlaylist: VideoPlaylistService,
42 private localStorageService: LocalStorageService,
43 private sessionStorageService: SessionStorageService,
44 private router: Router
45 ) {
46 // defaults to true
47 this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn()
48 ? this.auth.getUser().autoPlayNextVideoPlaylist
49 : this.localStorageService.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false'
50
51 this.setAutoPlayNextVideoPlaylistSwitchText()
52
53 // defaults to false
54 this.loopPlaylist = this.sessionStorageService.getItem(VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
55 this.setLoopPlaylistSwitchText()
56 }
57
58 onPlaylistVideosNearOfBottom (position?: number) {
59 // Last page
60 if (this.playlistPagination.totalItems <= (this.playlistPagination.currentPage * this.playlistPagination.itemsPerPage)) return
61
62 this.playlistPagination.currentPage += 1
63 this.loadPlaylistElements(this.playlist, false, position)
64 }
65
66 onElementRemoved (playlistElement: VideoPlaylistElement) {
67 this.playlistElements = this.playlistElements.filter(e => e.id !== playlistElement.id)
68
69 this.playlistPagination.totalItems--
70 }
71
72 isPlaylistOwned () {
73 return this.playlist.isLocal === true &&
74 this.auth.isLoggedIn() &&
75 this.playlist.ownerAccount.name === this.auth.getUser().username
76 }
77
78 isUnlistedPlaylist () {
79 return this.playlist.privacy.id === VideoPlaylistPrivacy.UNLISTED
80 }
81
82 isPrivatePlaylist () {
83 return this.playlist.privacy.id === VideoPlaylistPrivacy.PRIVATE
84 }
85
86 isPublicPlaylist () {
87 return this.playlist.privacy.id === VideoPlaylistPrivacy.PUBLIC
88 }
89
90 loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false, position?: number) {
91 this.videoPlaylist.getPlaylistVideos(playlist.uuid, this.playlistPagination)
92 .subscribe(({ total, data }) => {
93 this.playlistElements = this.playlistElements.concat(data)
94 this.playlistPagination.totalItems = total
95
96 const firstAvailableVideo = this.playlistElements.find(e => !!e.video)
97 if (!firstAvailableVideo) {
98 this.noPlaylistVideos = true
99 return
100 }
101
102 if (position) this.updatePlaylistIndex(position)
103
104 if (redirectToFirst) {
105 const extras = {
106 queryParams: {
107 start: firstAvailableVideo.startTimestamp,
108 stop: firstAvailableVideo.stopTimestamp,
109 playlistPosition: firstAvailableVideo.position
110 },
111 replaceUrl: true
112 }
113 this.router.navigate([], extras)
114 }
115 })
116 }
117
118 updatePlaylistIndex (position: number) {
119 if (this.playlistElements.length === 0 || !position) return
120
121 // Handle the reverse index
122 if (position < 0) position = this.playlist.videosLength + position + 1
123
124 for (const playlistElement of this.playlistElements) {
125 // >= if the previous videos were not valid
126 if (playlistElement.video && playlistElement.position >= position) {
127 this.currentPlaylistPosition = playlistElement.position
128
129 this.videoFound.emit(playlistElement.video.uuid)
130
131 setTimeout(() => {
132 document.querySelector('.element-' + this.currentPlaylistPosition).scrollIntoView(false)
133 }, 0)
134
135 return
136 }
137 }
138
139 // Load more videos to find our video
140 this.onPlaylistVideosNearOfBottom(position)
141 }
142
143 navigateToPreviousPlaylistVideo () {
144 const previous = this.findPlaylistVideo(this.currentPlaylistPosition - 1, 'previous')
145 if (!previous) return
146
147 const start = previous.startTimestamp
148 const stop = previous.stopTimestamp
149 this.router.navigate([],{ queryParams: { playlistPosition: previous.position, start, stop } })
150 }
151
152 findPlaylistVideo (position: number, type: 'previous' | 'next'): VideoPlaylistElement {
153 if (
154 (type === 'next' && position > this.playlistPagination.totalItems) ||
155 (type === 'previous' && position < 1)
156 ) {
157 // End of the playlist: end the recursion if we're not in the loop mode
158 if (!this.loopPlaylist) return
159
160 // Loop mode
161 position = type === 'previous'
162 ? this.playlistPagination.totalItems
163 : 1
164 }
165
166 const found = this.playlistElements.find(e => e.position === position)
167 if (found && found.video) return found
168
169 const newPosition = type === 'previous'
170 ? position - 1
171 : position + 1
172
173 return this.findPlaylistVideo(newPosition, type)
174 }
175
176 navigateToNextPlaylistVideo () {
177 const next = this.findPlaylistVideo(this.currentPlaylistPosition + 1, 'next')
178 if (!next) return
179
180 const start = next.startTimestamp
181 const stop = next.stopTimestamp
182 this.router.navigate([],{ queryParams: { playlistPosition: next.position, start, stop } })
183 }
184
185 switchAutoPlayNextVideoPlaylist () {
186 this.autoPlayNextVideoPlaylist = !this.autoPlayNextVideoPlaylist
187 this.setAutoPlayNextVideoPlaylistSwitchText()
188
189 peertubeLocalStorage.setItem(
190 VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST,
191 this.autoPlayNextVideoPlaylist.toString()
192 )
193
194 if (this.auth.isLoggedIn()) {
195 const details = {
196 autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist
197 }
198
199 this.userService.updateMyProfile(details).subscribe(
200 () => {
201 this.auth.refreshUserInformation()
202 },
203 err => this.notifier.error(err.message)
204 )
205 }
206 }
207
208 switchLoopPlaylist () {
209 this.loopPlaylist = !this.loopPlaylist
210 this.setLoopPlaylistSwitchText()
211
212 peertubeSessionStorage.setItem(
213 VideoWatchPlaylistComponent.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST,
214 this.loopPlaylist.toString()
215 )
216 }
217
218 private setAutoPlayNextVideoPlaylistSwitchText () {
219 this.autoPlayNextVideoPlaylistSwitchText = this.autoPlayNextVideoPlaylist
220 ? $localize`Stop autoplaying next video`
221 : $localize`Autoplay next video`
222 }
223
224 private setLoopPlaylistSwitchText () {
225 this.loopPlaylistSwitchText = this.loopPlaylist
226 ? $localize`Stop looping playlist videos`
227 : $localize`Loop playlist videos`
228 }
229}