diff options
Diffstat (limited to 'client/src/app')
20 files changed, 287 insertions, 229 deletions
diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts index 1814ef455..0d579fa0c 100644 --- a/client/src/app/+accounts/account-videos/account-videos.component.ts +++ b/client/src/app/+accounts/account-videos/account-videos.component.ts | |||
@@ -7,7 +7,7 @@ import { AbstractVideoList } from '../../shared/video/abstract-video-list' | |||
7 | import { VideoService } from '../../shared/video/video.service' | 7 | import { VideoService } from '../../shared/video/video.service' |
8 | import { Account } from '@app/shared/account/account.model' | 8 | import { Account } from '@app/shared/account/account.model' |
9 | import { AccountService } from '@app/shared/account/account.service' | 9 | import { AccountService } from '@app/shared/account/account.service' |
10 | import { tap } from 'rxjs/operators' | 10 | import { first, tap } from 'rxjs/operators' |
11 | import { I18n } from '@ngx-translate/i18n-polyfill' | 11 | import { I18n } from '@ngx-translate/i18n-polyfill' |
12 | import { Subscription } from 'rxjs' | 12 | import { Subscription } from 'rxjs' |
13 | import { ScreenService } from '@app/shared/misc/screen.service' | 13 | import { ScreenService } from '@app/shared/misc/screen.service' |
@@ -50,12 +50,13 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, | |||
50 | 50 | ||
51 | // Parent get the account for us | 51 | // Parent get the account for us |
52 | this.accountSub = this.accountService.accountLoaded | 52 | this.accountSub = this.accountService.accountLoaded |
53 | .subscribe(account => { | 53 | .pipe(first()) |
54 | this.account = account | 54 | .subscribe(account => { |
55 | this.account = account | ||
55 | 56 | ||
56 | this.reloadVideos() | 57 | this.reloadVideos() |
57 | this.generateSyndicationList() | 58 | this.generateSyndicationList() |
58 | }) | 59 | }) |
59 | } | 60 | } |
60 | 61 | ||
61 | ngOnDestroy () { | 62 | ngOnDestroy () { |
diff --git a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts index f878a5a24..907aefae1 100644 --- a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts +++ b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts | |||
@@ -1,9 +1,7 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | 1 | import { Component, OnDestroy, OnInit } from '@angular/core' |
2 | import { AuthService } from '../../core/auth' | ||
3 | import { ConfirmService } from '../../core/confirm' | 2 | import { ConfirmService } from '../../core/confirm' |
4 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 3 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
5 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 4 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
6 | import { flatMap } from 'rxjs/operators' | ||
7 | import { Subscription } from 'rxjs' | 5 | import { Subscription } from 'rxjs' |
8 | import { Notifier } from '@app/core' | 6 | import { Notifier } from '@app/core' |
9 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | 7 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' |
@@ -28,7 +26,6 @@ export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy { | |||
28 | private videoChannel: VideoChannel | 26 | private videoChannel: VideoChannel |
29 | 27 | ||
30 | constructor ( | 28 | constructor ( |
31 | private authService: AuthService, | ||
32 | private notifier: Notifier, | 29 | private notifier: Notifier, |
33 | private confirmService: ConfirmService, | 30 | private confirmService: ConfirmService, |
34 | private videoPlaylistService: VideoPlaylistService, | 31 | private videoPlaylistService: VideoPlaylistService, |
@@ -57,8 +54,7 @@ export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy { | |||
57 | } | 54 | } |
58 | 55 | ||
59 | private loadVideoPlaylists () { | 56 | private loadVideoPlaylists () { |
60 | this.authService.userInformationLoaded | 57 | this.videoPlaylistService.listChannelPlaylists(this.videoChannel) |
61 | .pipe(flatMap(() => this.videoPlaylistService.listChannelPlaylists(this.videoChannel))) | ||
62 | .subscribe(res => { | 58 | .subscribe(res => { |
63 | this.videoPlaylists = this.videoPlaylists.concat(res.data) | 59 | this.videoPlaylists = this.videoPlaylists.concat(res.data) |
64 | this.pagination.totalItems = res.total | 60 | this.pagination.totalItems = res.total |
diff --git a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts index 2045a095d..5e60b34b4 100644 --- a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts +++ b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts | |||
@@ -7,7 +7,7 @@ import { AbstractVideoList } from '../../shared/video/abstract-video-list' | |||
7 | import { VideoService } from '../../shared/video/video.service' | 7 | import { VideoService } from '../../shared/video/video.service' |
8 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 8 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
9 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 9 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
10 | import { tap } from 'rxjs/operators' | 10 | import { first, tap } from 'rxjs/operators' |
11 | import { I18n } from '@ngx-translate/i18n-polyfill' | 11 | import { I18n } from '@ngx-translate/i18n-polyfill' |
12 | import { Subscription } from 'rxjs' | 12 | import { Subscription } from 'rxjs' |
13 | import { ScreenService } from '@app/shared/misc/screen.service' | 13 | import { ScreenService } from '@app/shared/misc/screen.service' |
@@ -50,12 +50,13 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On | |||
50 | 50 | ||
51 | // Parent get the video channel for us | 51 | // Parent get the video channel for us |
52 | this.videoChannelSub = this.videoChannelService.videoChannelLoaded | 52 | this.videoChannelSub = this.videoChannelService.videoChannelLoaded |
53 | .subscribe(videoChannel => { | 53 | .pipe(first()) |
54 | this.videoChannel = videoChannel | 54 | .subscribe(videoChannel => { |
55 | this.videoChannel = videoChannel | ||
55 | 56 | ||
56 | this.reloadVideos() | 57 | this.reloadVideos() |
57 | this.generateSyndicationList() | 58 | this.generateSyndicationList() |
58 | }) | 59 | }) |
59 | } | 60 | } |
60 | 61 | ||
61 | ngOnDestroy () { | 62 | ngOnDestroy () { |
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index ad0588b99..915466af7 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -125,8 +125,8 @@ export class AppComponent implements OnInit { | |||
125 | try { | 125 | try { |
126 | resetScroll = false | 126 | resetScroll = false |
127 | 127 | ||
128 | const previousUrl = new URL(window.location.origin + e1.url) | 128 | const previousUrl = new URL(window.location.origin + e1.urlAfterRedirects) |
129 | const nextUrl = new URL(window.location.origin + e2.url) | 129 | const nextUrl = new URL(window.location.origin + e2.urlAfterRedirects) |
130 | 130 | ||
131 | if (previousUrl.pathname !== nextUrl.pathname) { | 131 | if (previousUrl.pathname !== nextUrl.pathname) { |
132 | resetScroll = true | 132 | resetScroll = true |
diff --git a/client/src/app/core/hotkeys/hotkeys.component.scss b/client/src/app/core/hotkeys/hotkeys.component.scss index 9af10b7c4..3aa0b6252 100644 --- a/client/src/app/core/hotkeys/hotkeys.component.scss +++ b/client/src/app/core/hotkeys/hotkeys.component.scss | |||
@@ -1,5 +1,6 @@ | |||
1 | .cfp-hotkeys-container { | 1 | .cfp-hotkeys-container { |
2 | display: table !important; | 2 | display: flex !important; |
3 | align-items: center; | ||
3 | position: fixed; | 4 | position: fixed; |
4 | overflow: auto; | 5 | overflow: auto; |
5 | width: 100%; | 6 | width: 100%; |
@@ -35,9 +36,7 @@ | |||
35 | 36 | ||
36 | .cfp-hotkeys { | 37 | .cfp-hotkeys { |
37 | width: 100%; | 38 | width: 100%; |
38 | height: 100%; | 39 | max-height: 100%; |
39 | display: table-cell; | ||
40 | vertical-align: middle; | ||
41 | } | 40 | } |
42 | 41 | ||
43 | .cfp-hotkeys table { | 42 | .cfp-hotkeys table { |
@@ -102,4 +101,4 @@ | |||
102 | .cfp-hotkeys { | 101 | .cfp-hotkeys { |
103 | font-size: 1.2em; | 102 | font-size: 1.2em; |
104 | } | 103 | } |
105 | } \ No newline at end of file | 104 | } |
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.html b/client/src/app/shared/menu/top-menu-dropdown.component.html index 54a8f9e80..35511ee62 100644 --- a/client/src/app/shared/menu/top-menu-dropdown.component.html +++ b/client/src/app/shared/menu/top-menu-dropdown.component.html | |||
@@ -3,7 +3,7 @@ | |||
3 | 3 | ||
4 | <a *ngIf="menuEntry.routerLink" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page">{{ menuEntry.label }}</a> | 4 | <a *ngIf="menuEntry.routerLink" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page">{{ menuEntry.label }}</a> |
5 | 5 | ||
6 | <div *ngIf="!menuEntry.routerLink" ngbDropdown container="body" class="parent-entry" #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)"> | 6 | <div *ngIf="!menuEntry.routerLink" ngbDropdown [container]="container" class="parent-entry" #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)"> |
7 | <span | 7 | <span |
8 | (mouseenter)="openDropdownOnHover(dropdown)" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownAnchor | 8 | (mouseenter)="openDropdownOnHover(dropdown)" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownAnchor |
9 | (click)="dropdownAnchorClicked(dropdown)" role="button" class="title-page" | 9 | (click)="dropdownAnchorClicked(dropdown)" role="button" class="title-page" |
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.ts b/client/src/app/shared/menu/top-menu-dropdown.component.ts index e951ea236..5ccdafb54 100644 --- a/client/src/app/shared/menu/top-menu-dropdown.component.ts +++ b/client/src/app/shared/menu/top-menu-dropdown.component.ts | |||
@@ -4,6 +4,7 @@ import { NavigationEnd, Router } from '@angular/router' | |||
4 | import { Subscription } from 'rxjs' | 4 | import { Subscription } from 'rxjs' |
5 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | 5 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' |
6 | import { GlobalIconName } from '@app/shared/images/global-icon.component' | 6 | import { GlobalIconName } from '@app/shared/images/global-icon.component' |
7 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
7 | 8 | ||
8 | export type TopMenuDropdownParam = { | 9 | export type TopMenuDropdownParam = { |
9 | label: string | 10 | label: string |
@@ -27,11 +28,15 @@ export class TopMenuDropdownComponent implements OnInit, OnDestroy { | |||
27 | 28 | ||
28 | suffixLabels: { [ parentLabel: string ]: string } | 29 | suffixLabels: { [ parentLabel: string ]: string } |
29 | hasIcons = false | 30 | hasIcons = false |
31 | container: undefined | 'body' = undefined | ||
30 | 32 | ||
31 | private openedOnHover = false | 33 | private openedOnHover = false |
32 | private routeSub: Subscription | 34 | private routeSub: Subscription |
33 | 35 | ||
34 | constructor (private router: Router) {} | 36 | constructor ( |
37 | private router: Router, | ||
38 | private screen: ScreenService | ||
39 | ) {} | ||
35 | 40 | ||
36 | ngOnInit () { | 41 | ngOnInit () { |
37 | this.updateChildLabels(window.location.pathname) | 42 | this.updateChildLabels(window.location.pathname) |
@@ -43,6 +48,12 @@ export class TopMenuDropdownComponent implements OnInit, OnDestroy { | |||
43 | this.hasIcons = this.menuEntries.some( | 48 | this.hasIcons = this.menuEntries.some( |
44 | e => e.children && e.children.some(c => !!c.iconName) | 49 | e => e.children && e.children.some(c => !!c.iconName) |
45 | ) | 50 | ) |
51 | |||
52 | // FIXME: We have to set body for the container to avoid because of scroll overflow on mobile view | ||
53 | // But this break our hovering system | ||
54 | if (this.screen.isInMobileView()) { | ||
55 | this.container = 'body' | ||
56 | } | ||
46 | } | 57 | } |
47 | 58 | ||
48 | ngOnDestroy () { | 59 | ngOnDestroy () { |
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.html b/client/src/app/shared/video/video-actions-dropdown.component.html index 300fe318a..ec03fa55d 100644 --- a/client/src/app/shared/video/video-actions-dropdown.component.html +++ b/client/src/app/shared/video/video-actions-dropdown.component.html | |||
@@ -11,7 +11,7 @@ | |||
11 | </div> | 11 | </div> |
12 | 12 | ||
13 | <my-action-dropdown | 13 | <my-action-dropdown |
14 | [actions]="videoActions" [label]="label" [entry]="{ video: video }" (mouseenter)="loadDropdownInformation()" | 14 | [actions]="videoActions" [label]="label" [entry]="{ video: video }" (click)="loadDropdownInformation()" |
15 | [buttonSize]="buttonSize" [placement]="placement" [buttonDirection]="buttonDirection" [buttonStyled]="buttonStyled" | 15 | [buttonSize]="buttonSize" [placement]="placement" [buttonDirection]="buttonDirection" [buttonStyled]="buttonStyled" |
16 | ></my-action-dropdown> | 16 | ></my-action-dropdown> |
17 | 17 | ||
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 787ef1188..ee2f44f9e 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 { Component, EventEmitter, Input, OnChanges, 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' |
@@ -126,6 +126,10 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
126 | return this.video.isUnblacklistableBy(this.user) | 126 | return this.video.isUnblacklistableBy(this.user) |
127 | } | 127 | } |
128 | 128 | ||
129 | isVideoDownloadable () { | ||
130 | return this.video && this.video instanceof VideoDetails && this.video.downloadEnabled | ||
131 | } | ||
132 | |||
129 | /* Action handlers */ | 133 | /* Action handlers */ |
130 | 134 | ||
131 | async unblacklistVideo () { | 135 | async unblacklistVideo () { |
@@ -195,7 +199,7 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
195 | { | 199 | { |
196 | label: this.i18n('Download'), | 200 | label: this.i18n('Download'), |
197 | handler: () => this.showDownloadModal(), | 201 | handler: () => this.showDownloadModal(), |
198 | isDisplayed: () => this.displayOptions.download, | 202 | isDisplayed: () => this.displayOptions.download && this.isVideoDownloadable(), |
199 | iconName: 'download' | 203 | iconName: 'download' |
200 | }, | 204 | }, |
201 | { | 205 | { |
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts index 8463e15d7..22f024656 100644 --- a/client/src/app/shared/video/video-details.model.ts +++ b/client/src/app/shared/video/video-details.model.ts | |||
@@ -52,4 +52,8 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { | |||
52 | getHlsPlaylist () { | 52 | getHlsPlaylist () { |
53 | return this.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) | 53 | return this.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) |
54 | } | 54 | } |
55 | |||
56 | hasHlsPlaylist () { | ||
57 | return !!this.getHlsPlaylist() | ||
58 | } | ||
55 | } | 59 | } |
diff --git a/client/src/app/shared/video/video-miniature.component.scss b/client/src/app/shared/video/video-miniature.component.scss index 6e173af99..d665ce021 100644 --- a/client/src/app/shared/video/video-miniature.component.scss +++ b/client/src/app/shared/video/video-miniature.component.scss | |||
@@ -2,6 +2,9 @@ | |||
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | @import '_miniature'; | 3 | @import '_miniature'; |
4 | 4 | ||
5 | $more-button-width: 41px; | ||
6 | $more-margin-right: 10px; | ||
7 | |||
5 | .video-miniature { | 8 | .video-miniature { |
6 | width: $video-miniature-width; | 9 | width: $video-miniature-width; |
7 | display: inline-flex; | 10 | display: inline-flex; |
@@ -14,7 +17,7 @@ | |||
14 | display: flex; | 17 | display: flex; |
15 | 18 | ||
16 | .video-miniature-information { | 19 | .video-miniature-information { |
17 | width: 200px; | 20 | width: $video-miniature-width - $more-button-width - $more-margin-right; |
18 | line-height: normal; | 21 | line-height: normal; |
19 | 22 | ||
20 | .video-miniature-name { | 23 | .video-miniature-name { |
@@ -61,7 +64,9 @@ | |||
61 | 64 | ||
62 | .video-actions { | 65 | .video-actions { |
63 | margin-top: 3px; | 66 | margin-top: 3px; |
64 | margin-right: 10px; | 67 | margin-right: $more-margin-right; |
68 | width: $more-button-width; | ||
69 | height: 30px; | ||
65 | 70 | ||
66 | /deep/ .dropdown-root:not(.show) { | 71 | /deep/ .dropdown-root:not(.show) { |
67 | opacity: 0; | 72 | opacity: 0; |
@@ -86,7 +91,7 @@ | |||
86 | top: -3px; | 91 | top: -3px; |
87 | 92 | ||
88 | /deep/ .dropdown-root { | 93 | /deep/ .dropdown-root { |
89 | display: block !important; | 94 | opacity: 1 !important; |
90 | } | 95 | } |
91 | } | 96 | } |
92 | } | 97 | } |
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts index e32b8cbc5..48475033c 100644 --- a/client/src/app/shared/video/video-miniature.component.ts +++ b/client/src/app/shared/video/video-miniature.component.ts | |||
@@ -74,7 +74,7 @@ export class VideoMiniatureComponent implements OnInit { | |||
74 | 74 | ||
75 | // We rely on mouseenter to lazy load actions | 75 | // We rely on mouseenter to lazy load actions |
76 | if (this.screenService.isInTouchScreen()) { | 76 | if (this.screenService.isInTouchScreen()) { |
77 | this.showActions = true | 77 | this.loadActions() |
78 | } | 78 | } |
79 | } | 79 | } |
80 | 80 | ||
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..bccdaf7b2 --- /dev/null +++ b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts | |||
@@ -0,0 +1,113 @@ | |||
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 && | ||
51 | this.auth.isLoggedIn() && | ||
52 | this.playlist.ownerAccount.name === this.auth.getUser().username | ||
53 | } | ||
54 | |||
55 | isUnlistedPlaylist () { | ||
56 | return this.playlist.privacy.id === VideoPlaylistPrivacy.UNLISTED | ||
57 | } | ||
58 | |||
59 | isPrivatePlaylist () { | ||
60 | return this.playlist.privacy.id === VideoPlaylistPrivacy.PRIVATE | ||
61 | } | ||
62 | |||
63 | isPublicPlaylist () { | ||
64 | return this.playlist.privacy.id === VideoPlaylistPrivacy.PUBLIC | ||
65 | } | ||
66 | |||
67 | loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false) { | ||
68 | this.videoService.getPlaylistVideos(playlist.uuid, this.playlistPagination) | ||
69 | .subscribe(({ totalVideos, videos }) => { | ||
70 | this.playlistVideos = this.playlistVideos.concat(videos) | ||
71 | this.playlistPagination.totalItems = totalVideos | ||
72 | |||
73 | if (totalVideos === 0) { | ||
74 | this.noPlaylistVideos = true | ||
75 | return | ||
76 | } | ||
77 | |||
78 | this.updatePlaylistIndex(this.video) | ||
79 | |||
80 | if (redirectToFirst) { | ||
81 | const extras = { | ||
82 | queryParams: { videoId: this.playlistVideos[ 0 ].uuid }, | ||
83 | replaceUrl: true | ||
84 | } | ||
85 | this.router.navigate([], extras) | ||
86 | } | ||
87 | }) | ||
88 | } | ||
89 | |||
90 | updatePlaylistIndex (video: VideoDetails) { | ||
91 | if (this.playlistVideos.length === 0 || !video) return | ||
92 | |||
93 | for (const playlistVideo of this.playlistVideos) { | ||
94 | if (playlistVideo.id === video.id) { | ||
95 | this.currentPlaylistPosition = playlistVideo.playlistElement.position | ||
96 | return | ||
97 | } | ||
98 | } | ||
99 | |||
100 | // Load more videos to find our video | ||
101 | this.onPlaylistVideosNearOfBottom() | ||
102 | } | ||
103 | |||
104 | navigateToNextPlaylistVideo () { | ||
105 | if (this.currentPlaylistPosition < this.playlistPagination.totalItems) { | ||
106 | const next = this.playlistVideos.find(v => v.playlistElement.position === this.currentPlaylistPosition + 1) | ||
107 | |||
108 | const start = next.playlistElement.startTimestamp | ||
109 | const stop = next.playlistElement.stopTimestamp | ||
110 | this.router.navigate([],{ queryParams: { videoId: next.uuid, start, stop } }) | ||
111 | } | ||
112 | } | ||
113 | } | ||
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..2e39b9c6b 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"> |
@@ -49,10 +28,6 @@ | |||
49 | This video will be published on {{ video.scheduledUpdate.updateAt | date: 'full' }}. | 28 | This video will be published on {{ video.scheduledUpdate.updateAt | date: 'full' }}. |
50 | </div> | 29 | </div> |
51 | 30 | ||
52 | <div i18n class="col-md-12 alert alert-info" *ngIf="noPlaylistVideos"> | ||
53 | This playlist does not have videos. | ||
54 | </div> | ||
55 | |||
56 | <div class="col-md-12 alert alert-danger" *ngIf="video?.blacklisted"> | 31 | <div class="col-md-12 alert alert-danger" *ngIf="video?.blacklisted"> |
57 | <div class="blacklisted-label" i18n>This video is blacklisted.</div> | 32 | <div class="blacklisted-label" i18n>This video is blacklisted.</div> |
58 | {{ video.blacklistedReason }} | 33 | {{ video.blacklistedReason }} |
@@ -240,6 +215,7 @@ | |||
240 | OK | 215 | OK |
241 | </div> | 216 | </div> |
242 | </div> | 217 | </div> |
218 | </div> | ||
243 | 219 | ||
244 | <ng-template [ngIf]="video !== null"> | 220 | <ng-template [ngIf]="video !== null"> |
245 | <my-video-support #videoSupportModal [video]="video"></my-video-support> | 221 | <my-video-support #videoSupportModal [video]="video"></my-video-support> |
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..b147b75b0 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 |
@@ -491,7 +423,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
491 | } | 423 | } |
492 | } | 424 | } |
493 | 425 | ||
494 | const mode: PlayerMode = urlOptions.playerMode === 'p2p-media-loader' ? 'p2p-media-loader' : 'webtorrent' | 426 | let mode: PlayerMode |
427 | |||
428 | if (urlOptions.playerMode) { | ||
429 | if (urlOptions.playerMode === 'p2p-media-loader') mode = 'p2p-media-loader' | ||
430 | else mode = 'webtorrent' | ||
431 | } else { | ||
432 | if (this.video.hasHlsPlaylist()) mode = 'p2p-media-loader' | ||
433 | else mode = 'webtorrent' | ||
434 | } | ||
495 | 435 | ||
496 | if (mode === 'p2p-media-loader') { | 436 | if (mode === 'p2p-media-loader') { |
497 | const hlsPlaylist = this.video.getHlsPlaylist() | 437 | const hlsPlaylist = this.video.getHlsPlaylist() |
@@ -519,13 +459,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
519 | 459 | ||
520 | this.player.one('ended', () => { | 460 | this.player.one('ended', () => { |
521 | if (this.playlist) { | 461 | if (this.playlist) { |
522 | this.zone.run(() => this.navigateToNextPlaylistVideo()) | 462 | this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo()) |
523 | } | 463 | } |
524 | }) | 464 | }) |
525 | 465 | ||
526 | this.player.one('stopped', () => { | 466 | this.player.one('stopped', () => { |
527 | if (this.playlist) { | 467 | if (this.playlist) { |
528 | this.zone.run(() => this.navigateToNextPlaylistVideo()) | 468 | this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo()) |
529 | } | 469 | } |
530 | }) | 470 | }) |
531 | 471 | ||
@@ -586,20 +526,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
586 | this.setVideoLikesBarTooltipText() | 526 | this.setVideoLikesBarTooltipText() |
587 | } | 527 | } |
588 | 528 | ||
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 () { | 529 | private setOpenGraphTags () { |
604 | this.metaService.setTitle(this.video.name) | 530 | this.metaService.setTitle(this.video.name) |
605 | 531 | ||
@@ -639,14 +565,4 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
639 | this.player = undefined | 565 | this.player = undefined |
640 | } | 566 | } |
641 | } | 567 | } |
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 | } | 568 | } |
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, |