diff options
Diffstat (limited to 'client')
31 files changed, 268 insertions, 151 deletions
diff --git a/client/angular.json b/client/angular.json index 789eeb3d0..2cf2ecd62 100644 --- a/client/angular.json +++ b/client/angular.json | |||
@@ -24,7 +24,7 @@ | |||
24 | }, | 24 | }, |
25 | "assets": [ | 25 | "assets": [ |
26 | "src/assets/images", | 26 | "src/assets/images", |
27 | "src/manifest.json" | 27 | "src/manifest.webmanifest" |
28 | ], | 28 | ], |
29 | "styles": [ | 29 | "styles": [ |
30 | "src/sass/application.scss" | 30 | "src/sass/application.scss" |
@@ -105,7 +105,7 @@ | |||
105 | ], | 105 | ], |
106 | "assets": [ | 106 | "assets": [ |
107 | "src/assets/images", | 107 | "src/assets/images", |
108 | "src/manifest.json" | 108 | "src/manifest.webmanifest" |
109 | ] | 109 | ] |
110 | } | 110 | } |
111 | }, | 111 | }, |
diff --git a/client/e2e/src/po/video-watch.po.ts b/client/e2e/src/po/video-watch.po.ts index 13f4ae945..e17aebc29 100644 --- a/client/e2e/src/po/video-watch.po.ts +++ b/client/e2e/src/po/video-watch.po.ts | |||
@@ -26,8 +26,11 @@ export class VideoWatchPage { | |||
26 | .then((texts: any) => texts.map(t => t.trim())) | 26 | .then((texts: any) => texts.map(t => t.trim())) |
27 | } | 27 | } |
28 | 28 | ||
29 | waitWatchVideoName (videoName: string, isSafari: boolean) { | 29 | waitWatchVideoName (videoName: string, isMobileDevice: boolean, isSafari: boolean) { |
30 | const elem = element(by.css('.video-info .video-info-name')) | 30 | // On mobile we display the first node, on desktop the second |
31 | const index = isMobileDevice ? 0 : 1 | ||
32 | |||
33 | const elem = element.all(by.css('.video-info .video-info-name')).get(index) | ||
31 | 34 | ||
32 | if (isSafari) return browser.sleep(5000) | 35 | if (isSafari) return browser.sleep(5000) |
33 | 36 | ||
diff --git a/client/e2e/src/videos.e2e-spec.ts b/client/e2e/src/videos.e2e-spec.ts index 3d4d46292..606b6ac5d 100644 --- a/client/e2e/src/videos.e2e-spec.ts +++ b/client/e2e/src/videos.e2e-spec.ts | |||
@@ -12,7 +12,7 @@ describe('Videos workflow', () => { | |||
12 | let isSafari = false | 12 | let isSafari = false |
13 | 13 | ||
14 | beforeEach(async () => { | 14 | beforeEach(async () => { |
15 | browser.waitForAngularEnabled(false) | 15 | await browser.waitForAngularEnabled(false) |
16 | 16 | ||
17 | videoWatchPage = new VideoWatchPage() | 17 | videoWatchPage = new VideoWatchPage() |
18 | pageUploadPage = new VideoUploadPage() | 18 | pageUploadPage = new VideoUploadPage() |
@@ -62,7 +62,7 @@ describe('Videos workflow', () => { | |||
62 | if (isMobileDevice || isSafari) videoNameToExcept = await videoWatchPage.clickOnFirstVideo() | 62 | if (isMobileDevice || isSafari) videoNameToExcept = await videoWatchPage.clickOnFirstVideo() |
63 | else await videoWatchPage.clickOnVideo(videoName) | 63 | else await videoWatchPage.clickOnVideo(videoName) |
64 | 64 | ||
65 | return videoWatchPage.waitWatchVideoName(videoNameToExcept, isSafari) | 65 | return videoWatchPage.waitWatchVideoName(videoNameToExcept, isMobileDevice, isSafari) |
66 | }) | 66 | }) |
67 | 67 | ||
68 | it('Should play the video', async () => { | 68 | it('Should play the video', async () => { |
diff --git a/client/src/app/+admin/moderation/moderation.routes.ts b/client/src/app/+admin/moderation/moderation.routes.ts index b133152d9..6d81b9b36 100644 --- a/client/src/app/+admin/moderation/moderation.routes.ts +++ b/client/src/app/+admin/moderation/moderation.routes.ts | |||
@@ -16,6 +16,16 @@ export const ModerationRoutes: Routes = [ | |||
16 | pathMatch: 'full' | 16 | pathMatch: 'full' |
17 | }, | 17 | }, |
18 | { | 18 | { |
19 | path: 'video-abuses', | ||
20 | redirectTo: 'video-abuses/list', | ||
21 | pathMatch: 'full' | ||
22 | }, | ||
23 | { | ||
24 | path: 'video-blacklist', | ||
25 | redirectTo: 'video-blacklist/list', | ||
26 | pathMatch: 'full' | ||
27 | }, | ||
28 | { | ||
19 | path: 'video-abuses/list', | 29 | path: 'video-abuses/list', |
20 | component: VideoAbuseListComponent, | 30 | component: VideoAbuseListComponent, |
21 | canActivate: [ UserRightGuard ], | 31 | canActivate: [ UserRightGuard ], |
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index 57e63d465..9697ce202 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts | |||
@@ -105,7 +105,8 @@ export class UserListComponent extends RestTable implements OnInit { | |||
105 | return | 105 | return |
106 | } | 106 | } |
107 | 107 | ||
108 | const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this user?'), this.i18n('Delete')) | 108 | const message = this.i18n('If you remove this user, you will not be able to create another with the same username!') |
109 | const res = await this.confirmService.confirm(message, this.i18n('Delete')) | ||
109 | if (res === false) return | 110 | if (res === false) return |
110 | 111 | ||
111 | this.userService.removeUser(user).subscribe( | 112 | this.userService.removeUser(user).subscribe( |
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts index 24cd5aa28..f13ecc2c7 100644 --- a/client/src/app/menu/menu.component.ts +++ b/client/src/app/menu/menu.component.ts | |||
@@ -19,8 +19,10 @@ export class MenuComponent implements OnInit { | |||
19 | private routesPerRight = { | 19 | private routesPerRight = { |
20 | [UserRight.MANAGE_USERS]: '/admin/users', | 20 | [UserRight.MANAGE_USERS]: '/admin/users', |
21 | [UserRight.MANAGE_SERVER_FOLLOW]: '/admin/friends', | 21 | [UserRight.MANAGE_SERVER_FOLLOW]: '/admin/friends', |
22 | [UserRight.MANAGE_VIDEO_ABUSES]: '/admin/video-abuses', | 22 | [UserRight.MANAGE_VIDEO_ABUSES]: '/admin/moderation/video-abuses', |
23 | [UserRight.MANAGE_VIDEO_BLACKLIST]: '/admin/video-blacklist' | 23 | [UserRight.MANAGE_VIDEO_BLACKLIST]: '/admin/moderation/video-blacklist', |
24 | [UserRight.MANAGE_JOBS]: '/admin/jobs', | ||
25 | [UserRight.MANAGE_CONFIGURATION]: '/admin/config' | ||
24 | } | 26 | } |
25 | 27 | ||
26 | constructor ( | 28 | constructor ( |
@@ -67,7 +69,9 @@ export class MenuComponent implements OnInit { | |||
67 | UserRight.MANAGE_USERS, | 69 | UserRight.MANAGE_USERS, |
68 | UserRight.MANAGE_SERVER_FOLLOW, | 70 | UserRight.MANAGE_SERVER_FOLLOW, |
69 | UserRight.MANAGE_VIDEO_ABUSES, | 71 | UserRight.MANAGE_VIDEO_ABUSES, |
70 | UserRight.MANAGE_VIDEO_BLACKLIST | 72 | UserRight.MANAGE_VIDEO_BLACKLIST, |
73 | UserRight.MANAGE_JOBS, | ||
74 | UserRight.MANAGE_CONFIGURATION | ||
71 | ] | 75 | ] |
72 | 76 | ||
73 | for (const adminRight of adminRights) { | 77 | for (const adminRight of adminRights) { |
diff --git a/client/src/app/shared/overview/overview.service.ts b/client/src/app/shared/overview/overview.service.ts index 4a4714af6..097079e6d 100644 --- a/client/src/app/shared/overview/overview.service.ts +++ b/client/src/app/shared/overview/overview.service.ts | |||
@@ -56,6 +56,8 @@ export class OverviewService { | |||
56 | } | 56 | } |
57 | } | 57 | } |
58 | 58 | ||
59 | if (observables.length === 0) return of(videosOverviewResult) | ||
60 | |||
59 | return forkJoin(observables) | 61 | return forkJoin(observables) |
60 | .pipe( | 62 | .pipe( |
61 | // Translate categories | 63 | // Translate categories |
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html index 0f48b9a64..d543ab7c1 100644 --- a/client/src/app/shared/video/abstract-video-list.html +++ b/client/src/app/shared/video/abstract-video-list.html | |||
@@ -7,12 +7,12 @@ | |||
7 | <div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div> | 7 | <div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div> |
8 | <div | 8 | <div |
9 | myInfiniteScroller | 9 | myInfiniteScroller |
10 | [pageHeight]="pageHeight" | 10 | [pageHeight]="pageHeight" [firstLoadedPage]="firstLoadedPage" |
11 | (nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)" | 11 | (nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)" |
12 | class="videos" #videosElement | 12 | class="videos" #videosElement |
13 | > | 13 | > |
14 | <div *ngFor="let videos of videoPages" class="videos-page"> | 14 | <div *ngFor="let videos of videoPages; trackBy: pageByVideoId" class="videos-page"> |
15 | <my-video-miniature *ngFor="let video of videos" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"></my-video-miniature> | 15 | <my-video-miniature *ngFor="let video of videos; trackBy: videoById" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"></my-video-miniature> |
16 | </div> | 16 | </div> |
17 | </div> | 17 | </div> |
18 | </div> | 18 | </div> |
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts index b8fd7f8eb..6a758ebe0 100644 --- a/client/src/app/shared/video/abstract-video-list.ts +++ b/client/src/app/shared/video/abstract-video-list.ts | |||
@@ -36,9 +36,10 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
36 | videoHeight: number | 36 | videoHeight: number |
37 | videoPages: Video[][] = [] | 37 | videoPages: Video[][] = [] |
38 | ownerDisplayType: OwnerDisplayType = 'account' | 38 | ownerDisplayType: OwnerDisplayType = 'account' |
39 | firstLoadedPage: number | ||
39 | 40 | ||
40 | protected baseVideoWidth = 215 | 41 | protected baseVideoWidth = 215 |
41 | protected baseVideoHeight = 230 | 42 | protected baseVideoHeight = 205 |
42 | 43 | ||
43 | protected abstract notificationsService: NotificationsService | 44 | protected abstract notificationsService: NotificationsService |
44 | protected abstract authService: AuthService | 45 | protected abstract authService: AuthService |
@@ -80,6 +81,15 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
80 | if (this.resizeSubscription) this.resizeSubscription.unsubscribe() | 81 | if (this.resizeSubscription) this.resizeSubscription.unsubscribe() |
81 | } | 82 | } |
82 | 83 | ||
84 | pageByVideoId (index: number, page: Video[]) { | ||
85 | // Video are unique in all pages | ||
86 | return page[0].id | ||
87 | } | ||
88 | |||
89 | videoById (index: number, video: Video) { | ||
90 | return video.id | ||
91 | } | ||
92 | |||
83 | onNearOfTop () { | 93 | onNearOfTop () { |
84 | this.previousPage() | 94 | this.previousPage() |
85 | } | 95 | } |
@@ -100,7 +110,11 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
100 | this.loadMoreVideos(this.pagination.currentPage) | 110 | this.loadMoreVideos(this.pagination.currentPage) |
101 | } | 111 | } |
102 | 112 | ||
103 | loadMoreVideos (page: number) { | 113 | loadMoreVideos (page: number, loadOnTop = false) { |
114 | this.adjustVideoPageHeight() | ||
115 | |||
116 | const currentY = window.scrollY | ||
117 | |||
104 | if (this.loadedPages[page] !== undefined) return | 118 | if (this.loadedPages[page] !== undefined) return |
105 | if (this.loadingPage[page] === true) return | 119 | if (this.loadingPage[page] === true) return |
106 | 120 | ||
@@ -111,6 +125,8 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
111 | ({ videos, totalVideos }) => { | 125 | ({ videos, totalVideos }) => { |
112 | this.loadingPage[page] = false | 126 | this.loadingPage[page] = false |
113 | 127 | ||
128 | if (this.firstLoadedPage === undefined || this.firstLoadedPage > page) this.firstLoadedPage = page | ||
129 | |||
114 | // Paging is too high, return to the first one | 130 | // Paging is too high, return to the first one |
115 | if (this.pagination.currentPage > 1 && totalVideos <= ((this.pagination.currentPage - 1) * this.pagination.itemsPerPage)) { | 131 | if (this.pagination.currentPage > 1 && totalVideos <= ((this.pagination.currentPage - 1) * this.pagination.itemsPerPage)) { |
116 | this.pagination.currentPage = 1 | 132 | this.pagination.currentPage = 1 |
@@ -125,8 +141,17 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
125 | // Initialize infinite scroller now we loaded the first page | 141 | // Initialize infinite scroller now we loaded the first page |
126 | if (Object.keys(this.loadedPages).length === 1) { | 142 | if (Object.keys(this.loadedPages).length === 1) { |
127 | // Wait elements creation | 143 | // Wait elements creation |
128 | setTimeout(() => this.infiniteScroller.initialize(), 500) | 144 | setTimeout(() => { |
145 | this.infiniteScroller.initialize() | ||
146 | |||
147 | // At our first load, we did not load the first page | ||
148 | // Load the previous page so the user can move on the top (and browser previous pages) | ||
149 | if (this.pagination.currentPage > 1) this.loadMoreVideos(this.pagination.currentPage - 1, true) | ||
150 | }, 500) | ||
129 | } | 151 | } |
152 | |||
153 | // Insert elements on the top but keep the scroll in the previous position | ||
154 | if (loadOnTop) setTimeout(() => { window.scrollTo(0, currentY + this.pageHeight) }, 0) | ||
130 | }, | 155 | }, |
131 | error => { | 156 | error => { |
132 | this.loadingPage[page] = false | 157 | this.loadingPage[page] = false |
@@ -150,7 +175,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
150 | const min = this.minPageLoaded() | 175 | const min = this.minPageLoaded() |
151 | 176 | ||
152 | if (min > 1) { | 177 | if (min > 1) { |
153 | this.loadMoreVideos(min - 1) | 178 | this.loadMoreVideos(min - 1, true) |
154 | } | 179 | } |
155 | } | 180 | } |
156 | 181 | ||
@@ -189,6 +214,13 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
189 | this.videoPages = Object.values(this.loadedPages) | 214 | this.videoPages = Object.values(this.loadedPages) |
190 | } | 215 | } |
191 | 216 | ||
217 | protected adjustVideoPageHeight () { | ||
218 | const numberOfPagesLoaded = Object.keys(this.loadedPages).length | ||
219 | if (!numberOfPagesLoaded) return | ||
220 | |||
221 | this.pageHeight = this.videosElement.nativeElement.offsetHeight / numberOfPagesLoaded | ||
222 | } | ||
223 | |||
192 | protected buildVideoHeight () { | 224 | protected buildVideoHeight () { |
193 | // Same ratios than base width/height | 225 | // Same ratios than base width/height |
194 | return this.videosElement.nativeElement.offsetWidth * (this.baseVideoHeight / this.baseVideoWidth) | 226 | return this.videosElement.nativeElement.offsetWidth * (this.baseVideoHeight / this.baseVideoWidth) |
diff --git a/client/src/app/shared/video/infinite-scroller.directive.ts b/client/src/app/shared/video/infinite-scroller.directive.ts index 4dc1f86e7..a02e9444a 100644 --- a/client/src/app/shared/video/infinite-scroller.directive.ts +++ b/client/src/app/shared/video/infinite-scroller.directive.ts | |||
@@ -6,10 +6,9 @@ import { fromEvent, Subscription } from 'rxjs' | |||
6 | selector: '[myInfiniteScroller]' | 6 | selector: '[myInfiniteScroller]' |
7 | }) | 7 | }) |
8 | export class InfiniteScrollerDirective implements OnInit, OnDestroy { | 8 | export class InfiniteScrollerDirective implements OnInit, OnDestroy { |
9 | private static PAGE_VIEW_TOP_MARGIN = 500 | ||
10 | |||
11 | @Input() containerHeight: number | 9 | @Input() containerHeight: number |
12 | @Input() pageHeight: number | 10 | @Input() pageHeight: number |
11 | @Input() firstLoadedPage = 1 | ||
13 | @Input() percentLimit = 70 | 12 | @Input() percentLimit = 70 |
14 | @Input() autoInit = false | 13 | @Input() autoInit = false |
15 | 14 | ||
@@ -23,6 +22,7 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy { | |||
23 | private scrollDownSub: Subscription | 22 | private scrollDownSub: Subscription |
24 | private scrollUpSub: Subscription | 23 | private scrollUpSub: Subscription |
25 | private pageChangeSub: Subscription | 24 | private pageChangeSub: Subscription |
25 | private middleScreen: number | ||
26 | 26 | ||
27 | constructor () { | 27 | constructor () { |
28 | this.decimalLimit = this.percentLimit / 100 | 28 | this.decimalLimit = this.percentLimit / 100 |
@@ -39,6 +39,8 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy { | |||
39 | } | 39 | } |
40 | 40 | ||
41 | initialize () { | 41 | initialize () { |
42 | this.middleScreen = window.innerHeight / 2 | ||
43 | |||
42 | // Emit the last value | 44 | // Emit the last value |
43 | const throttleOptions = { leading: true, trailing: true } | 45 | const throttleOptions = { leading: true, trailing: true } |
44 | 46 | ||
@@ -92,6 +94,11 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy { | |||
92 | } | 94 | } |
93 | 95 | ||
94 | private calculateCurrentPage (current: number) { | 96 | private calculateCurrentPage (current: number) { |
95 | return Math.max(1, Math.round((current + InfiniteScrollerDirective.PAGE_VIEW_TOP_MARGIN) / this.pageHeight)) | 97 | const scrollY = current + this.middleScreen |
98 | |||
99 | const page = Math.max(1, Math.ceil(scrollY / this.pageHeight)) | ||
100 | |||
101 | // Offset page | ||
102 | return page + (this.firstLoadedPage - 1) | ||
96 | } | 103 | } |
97 | } | 104 | } |
diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html index 9cf3fb321..cfc483018 100644 --- a/client/src/app/shared/video/video-miniature.component.html +++ b/client/src/app/shared/video/video-miniature.component.html | |||
@@ -1,11 +1,11 @@ | |||
1 | <div class="video-miniature"> | 1 | <div class="video-miniature"> |
2 | <my-video-thumbnail [video]="video" [nsfw]="isVideoBlur()"></my-video-thumbnail> | 2 | <my-video-thumbnail [video]="video" [nsfw]="isVideoBlur"></my-video-thumbnail> |
3 | 3 | ||
4 | <div class="video-miniature-information"> | 4 | <div class="video-miniature-information"> |
5 | <a | 5 | <a |
6 | tabindex="-1" | 6 | tabindex="-1" |
7 | class="video-miniature-name" | 7 | class="video-miniature-name" |
8 | [routerLink]="[ '/videos/watch', video.uuid ]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur() }" | 8 | [routerLink]="[ '/videos/watch', video.uuid ]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur }" |
9 | > | 9 | > |
10 | {{ video.name }} | 10 | {{ video.name }} |
11 | </a> | 11 | </a> |
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts index 07193ebd5..27098f4b4 100644 --- a/client/src/app/shared/video/video-miniature.component.ts +++ b/client/src/app/shared/video/video-miniature.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core' |
2 | import { User } from '../users' | 2 | import { User } from '../users' |
3 | import { Video } from './video.model' | 3 | import { Video } from './video.model' |
4 | import { ServerService } from '@app/core' | 4 | import { ServerService } from '@app/core' |
@@ -8,13 +8,16 @@ export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto' | |||
8 | @Component({ | 8 | @Component({ |
9 | selector: 'my-video-miniature', | 9 | selector: 'my-video-miniature', |
10 | styleUrls: [ './video-miniature.component.scss' ], | 10 | styleUrls: [ './video-miniature.component.scss' ], |
11 | templateUrl: './video-miniature.component.html' | 11 | templateUrl: './video-miniature.component.html', |
12 | changeDetection: ChangeDetectionStrategy.OnPush | ||
12 | }) | 13 | }) |
13 | export class VideoMiniatureComponent implements OnInit { | 14 | export class VideoMiniatureComponent implements OnInit { |
14 | @Input() user: User | 15 | @Input() user: User |
15 | @Input() video: Video | 16 | @Input() video: Video |
16 | @Input() ownerDisplayType: OwnerDisplayType = 'account' | 17 | @Input() ownerDisplayType: OwnerDisplayType = 'account' |
17 | 18 | ||
19 | isVideoBlur: boolean | ||
20 | |||
18 | private ownerDisplayTypeChosen: 'account' | 'videoChannel' | 21 | private ownerDisplayTypeChosen: 'account' | 'videoChannel' |
19 | 22 | ||
20 | constructor (private serverService: ServerService) { } | 23 | constructor (private serverService: ServerService) { } |
@@ -35,10 +38,8 @@ export class VideoMiniatureComponent implements OnInit { | |||
35 | } else { | 38 | } else { |
36 | this.ownerDisplayTypeChosen = 'videoChannel' | 39 | this.ownerDisplayTypeChosen = 'videoChannel' |
37 | } | 40 | } |
38 | } | ||
39 | 41 | ||
40 | isVideoBlur () { | 42 | this.isVideoBlur = this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig()) |
41 | return this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig()) | ||
42 | } | 43 | } |
43 | 44 | ||
44 | displayOwnerAccount () { | 45 | displayOwnerAccount () { |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html index 8c0723155..ff0e45413 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html +++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html | |||
@@ -22,7 +22,7 @@ | |||
22 | <div class="peertube-select-container"> | 22 | <div class="peertube-select-container"> |
23 | <select id="first-step-privacy" [(ngModel)]="firstStepPrivacyId"> | 23 | <select id="first-step-privacy" [(ngModel)]="firstStepPrivacyId"> |
24 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> | 24 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> |
25 | <option [value]="SPECIAL_SCHEDULED_PRIVACY">Scheduled</option> | 25 | <option i18n [value]="SPECIAL_SCHEDULED_PRIVACY">Scheduled</option> |
26 | </select> | 26 | </select> |
27 | </div> | 27 | </div> |
28 | </div> | 28 | </div> |
diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss b/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss index a55e743fb..bb809296a 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss +++ b/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss | |||
@@ -39,3 +39,9 @@ form { | |||
39 | @include orange-button | 39 | @include orange-button |
40 | } | 40 | } |
41 | } | 41 | } |
42 | |||
43 | @media screen and (max-width: 450px) { | ||
44 | textarea, .submit-comment button { | ||
45 | font-size: 14px !important; | ||
46 | } | ||
47 | } \ No newline at end of file | ||
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.component.scss b/client/src/app/videos/+video-watch/comment/video-comment.component.scss index f331fab80..84da5727e 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.component.scss +++ b/client/src/app/videos/+video-watch/comment/video-comment.component.scss | |||
@@ -35,6 +35,7 @@ | |||
35 | .comment-account { | 35 | .comment-account { |
36 | @include disable-default-a-behaviour; | 36 | @include disable-default-a-behaviour; |
37 | 37 | ||
38 | word-break: break-all; | ||
38 | color: var(--mainForegroundColor); | 39 | color: var(--mainForegroundColor); |
39 | font-weight: $font-bold; | 40 | font-weight: $font-bold; |
40 | } | 41 | } |
@@ -102,3 +103,9 @@ | |||
102 | img { margin-right: 10px; } | 103 | img { margin-right: 10px; } |
103 | } | 104 | } |
104 | } | 105 | } |
106 | |||
107 | @media screen and (max-width: 450px) { | ||
108 | .root-comment { | ||
109 | font-size: 14px; | ||
110 | } | ||
111 | } \ No newline at end of file | ||
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.scss b/client/src/app/videos/+video-watch/comment/video-comments.component.scss index d5af929d7..04518e079 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.scss +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.scss | |||
@@ -31,4 +31,10 @@ my-help { | |||
31 | .view-replies { | 31 | .view-replies { |
32 | margin-left: 46px; | 32 | margin-left: 46px; |
33 | } | 33 | } |
34 | } \ No newline at end of file | 34 | } |
35 | |||
36 | @media screen and (max-width: 450px) { | ||
37 | .view-replies { | ||
38 | font-size: 14px; | ||
39 | } | ||
40 | } | ||
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 fac4bdbe5..eb63cbde7 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss | |||
@@ -81,6 +81,7 @@ | |||
81 | flex-grow: 1; | 81 | flex-grow: 1; |
82 | // Set min width for flex item | 82 | // Set min width for flex item |
83 | min-width: 1px; | 83 | min-width: 1px; |
84 | max-width: 100%; | ||
84 | 85 | ||
85 | .video-info-first-row { | 86 | .video-info-first-row { |
86 | display: flex; | 87 | display: flex; |
@@ -472,6 +473,7 @@ my-video-comments { | |||
472 | margin: 20px 0 0 0; | 473 | margin: 20px 0 0 0; |
473 | 474 | ||
474 | .video-info { | 475 | .video-info { |
476 | padding: 0; | ||
475 | 477 | ||
476 | .video-info-first-row { | 478 | .video-info-first-row { |
477 | 479 | ||
@@ -484,6 +486,8 @@ my-video-comments { | |||
484 | } | 486 | } |
485 | 487 | ||
486 | /deep/ .other-videos { | 488 | /deep/ .other-videos { |
489 | padding-left: 0 !important; | ||
490 | |||
487 | /deep/ .video-miniature { | 491 | /deep/ .video-miniature { |
488 | flex-direction: column; | 492 | flex-direction: column; |
489 | } | 493 | } |
@@ -499,7 +503,27 @@ my-video-comments { | |||
499 | } | 503 | } |
500 | 504 | ||
501 | @media screen and (max-width: 450px) { | 505 | @media screen and (max-width: 450px) { |
502 | .video-bottom .action-button .icon-text { | 506 | .video-bottom { |
503 | display: none !important; | 507 | .action-button .icon-text { |
508 | display: none !important; | ||
509 | } | ||
510 | |||
511 | .video-info .video-info-first-row { | ||
512 | .video-info-name { | ||
513 | font-size: 18px; | ||
514 | } | ||
515 | |||
516 | .video-info-date-views { | ||
517 | font-size: 14px; | ||
518 | } | ||
519 | |||
520 | .video-actions-rates { | ||
521 | margin-top: 10px; | ||
522 | } | ||
523 | } | ||
524 | |||
525 | .video-info-description { | ||
526 | font-size: 14px !important; | ||
527 | } | ||
504 | } | 528 | } |
505 | } | 529 | } |
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 834428fa4..7a61e355a 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { catchError, subscribeOn } from 'rxjs/operators' | 1 | import { catchError } from 'rxjs/operators' |
2 | import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' | 2 | import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { RedirectService } from '@app/core/routing/redirect.service' | 4 | import { RedirectService } from '@app/core/routing/redirect.service' |
diff --git a/client/src/app/videos/recommendations/recent-videos-recommendation.service.ts b/client/src/app/videos/recommendations/recent-videos-recommendation.service.ts index 4723f7fd0..0ee34b9cb 100644 --- a/client/src/app/videos/recommendations/recent-videos-recommendation.service.ts +++ b/client/src/app/videos/recommendations/recent-videos-recommendation.service.ts | |||
@@ -25,8 +25,8 @@ export class RecentVideosRecommendationService implements RecommendationService | |||
25 | getRecommendations (recommendation: RecommendationInfo): Observable<Video[]> { | 25 | getRecommendations (recommendation: RecommendationInfo): Observable<Video[]> { |
26 | return this.fetchPage(1, recommendation) | 26 | return this.fetchPage(1, recommendation) |
27 | .pipe( | 27 | .pipe( |
28 | map(vids => { | 28 | map(videos => { |
29 | const otherVideos = vids.filter(v => v.uuid !== recommendation.uuid) | 29 | const otherVideos = videos.filter(v => v.uuid !== recommendation.uuid) |
30 | return otherVideos.slice(0, this.pageSize) | 30 | return otherVideos.slice(0, this.pageSize) |
31 | }) | 31 | }) |
32 | ) | 32 | ) |
diff --git a/client/src/app/videos/recommendations/recommended-videos.store.ts b/client/src/app/videos/recommendations/recommended-videos.store.ts index eb5c9867f..858ec3a27 100644 --- a/client/src/app/videos/recommendations/recommended-videos.store.ts +++ b/client/src/app/videos/recommendations/recommended-videos.store.ts | |||
@@ -3,8 +3,8 @@ import { Observable, ReplaySubject } from 'rxjs' | |||
3 | import { Video } from '@app/shared/video/video.model' | 3 | import { Video } from '@app/shared/video/video.model' |
4 | import { RecommendationInfo } from '@app/shared/video/recommendation-info.model' | 4 | import { RecommendationInfo } from '@app/shared/video/recommendation-info.model' |
5 | import { RecentVideosRecommendationService } from '@app/videos/recommendations/recent-videos-recommendation.service' | 5 | import { RecentVideosRecommendationService } from '@app/videos/recommendations/recent-videos-recommendation.service' |
6 | import { RecommendationService, UUID } from '@app/videos/recommendations/recommendations.service' | 6 | import { RecommendationService } from '@app/videos/recommendations/recommendations.service' |
7 | import { map, switchMap, take } from 'rxjs/operators' | 7 | import { map, shareReplay, switchMap, take } from 'rxjs/operators' |
8 | 8 | ||
9 | /** | 9 | /** |
10 | * This store is intended to provide data for the RecommendedVideosComponent. | 10 | * This store is intended to provide data for the RecommendedVideosComponent. |
@@ -19,9 +19,13 @@ export class RecommendedVideosStore { | |||
19 | @Inject(RecentVideosRecommendationService) private recommendations: RecommendationService | 19 | @Inject(RecentVideosRecommendationService) private recommendations: RecommendationService |
20 | ) { | 20 | ) { |
21 | this.recommendations$ = this.requestsForLoad$$.pipe( | 21 | this.recommendations$ = this.requestsForLoad$$.pipe( |
22 | switchMap(requestedRecommendation => recommendations.getRecommendations(requestedRecommendation) | 22 | switchMap(requestedRecommendation => { |
23 | .pipe(take(1)) | 23 | return recommendations.getRecommendations(requestedRecommendation) |
24 | )) | 24 | .pipe(take(1)) |
25 | }), | ||
26 | shareReplay() | ||
27 | ) | ||
28 | |||
25 | this.hasRecommendations$ = this.recommendations$.pipe( | 29 | this.hasRecommendations$ = this.recommendations$.pipe( |
26 | map(otherVideos => otherVideos.length > 0) | 30 | map(otherVideos => otherVideos.length > 0) |
27 | ) | 31 | ) |
diff --git a/client/src/app/videos/video-list/video-overview.component.html b/client/src/app/videos/video-list/video-overview.component.html index 4150cd5e1..4dad6a6e4 100644 --- a/client/src/app/videos/video-list/video-overview.component.html +++ b/client/src/app/videos/video-list/video-overview.component.html | |||
@@ -12,7 +12,7 @@ | |||
12 | 12 | ||
13 | <div class="section" *ngFor="let object of overview.tags"> | 13 | <div class="section" *ngFor="let object of overview.tags"> |
14 | <div class="section-title" i18n> | 14 | <div class="section-title" i18n> |
15 | <a routerLink="/search" [queryParams]="{ tagOneOf: [ object.tag ] }">{{ object.tag }}</a> | 15 | <a routerLink="/search" [queryParams]="{ tagsOneOf: [ object.tag ] }">{{ object.tag }}</a> |
16 | </div> | 16 | </div> |
17 | 17 | ||
18 | <my-video-miniature *ngFor="let video of object.videos" [video]="video" [user]="user"></my-video-miniature> | 18 | <my-video-miniature *ngFor="let video of object.videos" [video]="video" [user]="user"></my-video-miniature> |
diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts index 4b0677fab..36b80bd72 100644 --- a/client/src/assets/player/peertube-videojs-plugin.ts +++ b/client/src/assets/player/peertube-videojs-plugin.ts | |||
@@ -4,7 +4,7 @@ import { VideoFile } from '../../../../shared/models/videos/video.model' | |||
4 | import { renderVideo } from './video-renderer' | 4 | import { renderVideo } from './video-renderer' |
5 | import './settings-menu-button' | 5 | import './settings-menu-button' |
6 | import { PeertubePluginOptions, VideoJSCaption, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' | 6 | import { PeertubePluginOptions, VideoJSCaption, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' |
7 | import { isMobile, videoFileMaxByResolution, videoFileMinByResolution, timeToInt } from './utils' | 7 | import { isMobile, timeToInt, videoFileMaxByResolution, videoFileMinByResolution } from './utils' |
8 | import * as CacheChunkStore from 'cache-chunk-store' | 8 | import * as CacheChunkStore from 'cache-chunk-store' |
9 | import { PeertubeChunkStore } from './peertube-chunk-store' | 9 | import { PeertubeChunkStore } from './peertube-chunk-store' |
10 | import { | 10 | import { |
@@ -83,11 +83,6 @@ class PeerTubePlugin extends Plugin { | |||
83 | this.videoCaptions = options.videoCaptions | 83 | this.videoCaptions = options.videoCaptions |
84 | 84 | ||
85 | this.savePlayerSrcFunction = this.player.src | 85 | this.savePlayerSrcFunction = this.player.src |
86 | // Hack to "simulate" src link in video.js >= 6 | ||
87 | // Without this, we can't play the video after pausing it | ||
88 | // https://github.com/videojs/video.js/blob/master/src/js/player.js#L1633 | ||
89 | this.player.src = () => true | ||
90 | |||
91 | this.playerElement = options.playerElement | 86 | this.playerElement = options.playerElement |
92 | 87 | ||
93 | if (this.autoplay === true) this.player.addClass('vjs-has-autoplay') | 88 | if (this.autoplay === true) this.player.addClass('vjs-has-autoplay') |
@@ -104,9 +99,7 @@ class PeerTubePlugin extends Plugin { | |||
104 | 99 | ||
105 | this.player.one('play', () => { | 100 | this.player.one('play', () => { |
106 | // Don't run immediately scheduler, wait some seconds the TCP connections are made | 101 | // Don't run immediately scheduler, wait some seconds the TCP connections are made |
107 | this.runAutoQualitySchedulerTimer = setTimeout(() => { | 102 | this.runAutoQualitySchedulerTimer = setTimeout(() => this.runAutoQualityScheduler(), this.CONSTANTS.AUTO_QUALITY_SCHEDULER) |
108 | this.runAutoQualityScheduler() | ||
109 | }, this.CONSTANTS.AUTO_QUALITY_SCHEDULER) | ||
110 | }) | 103 | }) |
111 | }) | 104 | }) |
112 | 105 | ||
@@ -167,6 +160,9 @@ class PeerTubePlugin extends Plugin { | |||
167 | // Do not display error to user because we will have multiple fallback | 160 | // Do not display error to user because we will have multiple fallback |
168 | this.disableErrorDisplay() | 161 | this.disableErrorDisplay() |
169 | 162 | ||
163 | // Hack to "simulate" src link in video.js >= 6 | ||
164 | // Without this, we can't play the video after pausing it | ||
165 | // https://github.com/videojs/video.js/blob/master/src/js/player.js#L1633 | ||
170 | this.player.src = () => true | 166 | this.player.src = () => true |
171 | const oldPlaybackRate = this.player.playbackRate() | 167 | const oldPlaybackRate = this.player.playbackRate() |
172 | 168 | ||
@@ -181,7 +177,66 @@ class PeerTubePlugin extends Plugin { | |||
181 | this.trigger('videoFileUpdate') | 177 | this.trigger('videoFileUpdate') |
182 | } | 178 | } |
183 | 179 | ||
184 | addTorrent ( | 180 | updateResolution (resolutionId: number, delay = 0) { |
181 | // Remember player state | ||
182 | const currentTime = this.player.currentTime() | ||
183 | const isPaused = this.player.paused() | ||
184 | |||
185 | // Remove poster to have black background | ||
186 | this.playerElement.poster = '' | ||
187 | |||
188 | // Hide bigPlayButton | ||
189 | if (!isPaused) { | ||
190 | this.player.bigPlayButton.hide() | ||
191 | } | ||
192 | |||
193 | const newVideoFile = this.videoFiles.find(f => f.resolution.id === resolutionId) | ||
194 | const options = { | ||
195 | forcePlay: false, | ||
196 | delay, | ||
197 | seek: currentTime + (delay / 1000) | ||
198 | } | ||
199 | this.updateVideoFile(newVideoFile, options) | ||
200 | } | ||
201 | |||
202 | flushVideoFile (videoFile: VideoFile, destroyRenderer = true) { | ||
203 | if (videoFile !== undefined && this.webtorrent.get(videoFile.magnetUri)) { | ||
204 | if (destroyRenderer === true && this.renderer && this.renderer.destroy) this.renderer.destroy() | ||
205 | |||
206 | this.webtorrent.remove(videoFile.magnetUri) | ||
207 | console.log('Removed ' + videoFile.magnetUri) | ||
208 | } | ||
209 | } | ||
210 | |||
211 | isAutoResolutionOn () { | ||
212 | return this.autoResolution | ||
213 | } | ||
214 | |||
215 | enableAutoResolution () { | ||
216 | this.autoResolution = true | ||
217 | this.trigger('autoResolutionUpdate') | ||
218 | } | ||
219 | |||
220 | disableAutoResolution (forbid = false) { | ||
221 | if (forbid === true) this.forbidAutoResolution = true | ||
222 | |||
223 | this.autoResolution = false | ||
224 | this.trigger('autoResolutionUpdate') | ||
225 | } | ||
226 | |||
227 | isAutoResolutionForbidden () { | ||
228 | return this.forbidAutoResolution === true | ||
229 | } | ||
230 | |||
231 | getCurrentVideoFile () { | ||
232 | return this.currentVideoFile | ||
233 | } | ||
234 | |||
235 | getTorrent () { | ||
236 | return this.torrent | ||
237 | } | ||
238 | |||
239 | private addTorrent ( | ||
185 | magnetOrTorrentUrl: string, | 240 | magnetOrTorrentUrl: string, |
186 | previousVideoFile: VideoFile, | 241 | previousVideoFile: VideoFile, |
187 | options: { | 242 | options: { |
@@ -205,26 +260,15 @@ class PeerTubePlugin extends Plugin { | |||
205 | 260 | ||
206 | if (oldTorrent) { | 261 | if (oldTorrent) { |
207 | // Pause the old torrent | 262 | // Pause the old torrent |
208 | oldTorrent.pause() | 263 | this.stopTorrent(oldTorrent) |
209 | // Pause does not remove actual peers (in particular the webseed peer) | ||
210 | oldTorrent.removePeer(oldTorrent['ws']) | ||
211 | 264 | ||
212 | // We use a fake renderer so we download correct pieces of the next file | 265 | // We use a fake renderer so we download correct pieces of the next file |
213 | if (options.delay) { | 266 | if (options.delay) this.renderFileInFakeElement(torrent.files[ 0 ], options.delay) |
214 | const fakeVideoElem = document.createElement('video') | ||
215 | renderVideo(torrent.files[0], fakeVideoElem, { autoplay: false, controls: false }, (err, renderer) => { | ||
216 | this.fakeRenderer = renderer | ||
217 | |||
218 | if (err) console.error('Cannot render new torrent in fake video element.', err) | ||
219 | |||
220 | // Load the future file at the correct time | ||
221 | fakeVideoElem.currentTime = this.player.currentTime() + (options.delay / 2000) | ||
222 | }) | ||
223 | } | ||
224 | } | 267 | } |
225 | 268 | ||
226 | // Render the video in a few seconds? (on resolution change for example, we wait some seconds of the new video resolution) | 269 | // Render the video in a few seconds? (on resolution change for example, we wait some seconds of the new video resolution) |
227 | this.addTorrentDelay = setTimeout(() => { | 270 | this.addTorrentDelay = setTimeout(() => { |
271 | // We don't need the fake renderer anymore | ||
228 | this.destroyFakeRenderer() | 272 | this.destroyFakeRenderer() |
229 | 273 | ||
230 | const paused = this.player.paused() | 274 | const paused = this.player.paused() |
@@ -232,7 +276,7 @@ class PeerTubePlugin extends Plugin { | |||
232 | this.flushVideoFile(previousVideoFile) | 276 | this.flushVideoFile(previousVideoFile) |
233 | 277 | ||
234 | const renderVideoOptions = { autoplay: false, controls: true } | 278 | const renderVideoOptions = { autoplay: false, controls: true } |
235 | renderVideo(torrent.files[0], this.playerElement, renderVideoOptions,(err, renderer) => { | 279 | renderVideo(torrent.files[ 0 ], this.playerElement, renderVideoOptions, (err, renderer) => { |
236 | this.renderer = renderer | 280 | this.renderer = renderer |
237 | 281 | ||
238 | if (err) return this.fallbackToHttp(done) | 282 | if (err) return this.fallbackToHttp(done) |
@@ -265,7 +309,7 @@ class PeerTubePlugin extends Plugin { | |||
265 | if (err.message.indexOf('incorrect info hash') !== -1) { | 309 | if (err.message.indexOf('incorrect info hash') !== -1) { |
266 | console.error('Incorrect info hash detected, falling back to torrent file.') | 310 | console.error('Incorrect info hash detected, falling back to torrent file.') |
267 | const newOptions = { forcePlay: true, seek: options.seek } | 311 | const newOptions = { forcePlay: true, seek: options.seek } |
268 | return this.addTorrent(this.torrent['xs'], previousVideoFile, newOptions, done) | 312 | return this.addTorrent(this.torrent[ 'xs' ], previousVideoFile, newOptions, done) |
269 | } | 313 | } |
270 | 314 | ||
271 | // Remote instance is down | 315 | // Remote instance is down |
@@ -277,65 +321,6 @@ class PeerTubePlugin extends Plugin { | |||
277 | }) | 321 | }) |
278 | } | 322 | } |
279 | 323 | ||
280 | updateResolution (resolutionId: number, delay = 0) { | ||
281 | // Remember player state | ||
282 | const currentTime = this.player.currentTime() | ||
283 | const isPaused = this.player.paused() | ||
284 | |||
285 | // Remove poster to have black background | ||
286 | this.playerElement.poster = '' | ||
287 | |||
288 | // Hide bigPlayButton | ||
289 | if (!isPaused) { | ||
290 | this.player.bigPlayButton.hide() | ||
291 | } | ||
292 | |||
293 | const newVideoFile = this.videoFiles.find(f => f.resolution.id === resolutionId) | ||
294 | const options = { | ||
295 | forcePlay: false, | ||
296 | delay, | ||
297 | seek: currentTime + (delay / 1000) | ||
298 | } | ||
299 | this.updateVideoFile(newVideoFile, options) | ||
300 | } | ||
301 | |||
302 | flushVideoFile (videoFile: VideoFile, destroyRenderer = true) { | ||
303 | if (videoFile !== undefined && this.webtorrent.get(videoFile.magnetUri)) { | ||
304 | if (destroyRenderer === true && this.renderer && this.renderer.destroy) this.renderer.destroy() | ||
305 | |||
306 | this.webtorrent.remove(videoFile.magnetUri) | ||
307 | console.log('Removed ' + videoFile.magnetUri) | ||
308 | } | ||
309 | } | ||
310 | |||
311 | isAutoResolutionOn () { | ||
312 | return this.autoResolution | ||
313 | } | ||
314 | |||
315 | enableAutoResolution () { | ||
316 | this.autoResolution = true | ||
317 | this.trigger('autoResolutionUpdate') | ||
318 | } | ||
319 | |||
320 | disableAutoResolution (forbid = false) { | ||
321 | if (forbid === true) this.forbidAutoResolution = true | ||
322 | |||
323 | this.autoResolution = false | ||
324 | this.trigger('autoResolutionUpdate') | ||
325 | } | ||
326 | |||
327 | isAutoResolutionForbidden () { | ||
328 | return this.forbidAutoResolution === true | ||
329 | } | ||
330 | |||
331 | getCurrentVideoFile () { | ||
332 | return this.currentVideoFile | ||
333 | } | ||
334 | |||
335 | getTorrent () { | ||
336 | return this.torrent | ||
337 | } | ||
338 | |||
339 | private tryToPlay (done?: Function) { | 324 | private tryToPlay (done?: Function) { |
340 | if (!done) done = function () { /* empty */ } | 325 | if (!done) done = function () { /* empty */ } |
341 | 326 | ||
@@ -435,22 +420,22 @@ class PeerTubePlugin extends Plugin { | |||
435 | if (this.autoplay === true) { | 420 | if (this.autoplay === true) { |
436 | this.player.posterImage.hide() | 421 | this.player.posterImage.hide() |
437 | 422 | ||
438 | this.updateVideoFile(undefined, { forcePlay: true, seek: this.startTime }) | 423 | return this.updateVideoFile(undefined, { forcePlay: true, seek: this.startTime }) |
439 | } else { | 424 | } |
440 | // Don't try on iOS that does not support MediaSource | ||
441 | if (this.isIOS()) { | ||
442 | this.currentVideoFile = this.pickAverageVideoFile() | ||
443 | return this.fallbackToHttp(undefined, false) | ||
444 | } | ||
445 | 425 | ||
446 | // Proxy first play | 426 | // Don't try on iOS that does not support MediaSource |
447 | const oldPlay = this.player.play.bind(this.player) | 427 | if (this.isIOS()) { |
448 | this.player.play = () => { | 428 | this.currentVideoFile = this.pickAverageVideoFile() |
449 | this.player.addClass('vjs-has-big-play-button-clicked') | 429 | return this.fallbackToHttp(undefined, false) |
450 | this.player.play = oldPlay | 430 | } |
451 | 431 | ||
452 | this.updateVideoFile(undefined, { forcePlay: true, seek: this.startTime }) | 432 | // Proxy first play |
453 | } | 433 | const oldPlay = this.player.play.bind(this.player) |
434 | this.player.play = () => { | ||
435 | this.player.addClass('vjs-has-big-play-button-clicked') | ||
436 | this.player.play = oldPlay | ||
437 | |||
438 | this.updateVideoFile(undefined, { forcePlay: true, seek: this.startTime }) | ||
454 | } | 439 | } |
455 | } | 440 | } |
456 | 441 | ||
@@ -607,6 +592,24 @@ class PeerTubePlugin extends Plugin { | |||
607 | return this.videoFiles[Math.floor(this.videoFiles.length / 2)] | 592 | return this.videoFiles[Math.floor(this.videoFiles.length / 2)] |
608 | } | 593 | } |
609 | 594 | ||
595 | private stopTorrent (torrent: WebTorrent.Torrent) { | ||
596 | torrent.pause() | ||
597 | // Pause does not remove actual peers (in particular the webseed peer) | ||
598 | torrent.removePeer(torrent[ 'ws' ]) | ||
599 | } | ||
600 | |||
601 | private renderFileInFakeElement (file: WebTorrent.TorrentFile, delay: number) { | ||
602 | const fakeVideoElem = document.createElement('video') | ||
603 | renderVideo(file, fakeVideoElem, { autoplay: false, controls: false }, (err, renderer) => { | ||
604 | this.fakeRenderer = renderer | ||
605 | |||
606 | if (err) console.error('Cannot render new torrent in fake video element.', err) | ||
607 | |||
608 | // Load the future file at the correct time (in delay MS - 2 seconds) | ||
609 | fakeVideoElem.currentTime = this.player.currentTime() + (delay - 2000) | ||
610 | }) | ||
611 | } | ||
612 | |||
610 | private destroyFakeRenderer () { | 613 | private destroyFakeRenderer () { |
611 | if (this.fakeRenderer) { | 614 | if (this.fakeRenderer) { |
612 | if (this.fakeRenderer.destroy) { | 615 | if (this.fakeRenderer.destroy) { |
diff --git a/client/src/assets/player/settings-menu-item.ts b/client/src/assets/player/settings-menu-item.ts index 6e2224e20..f6cf6d0f3 100644 --- a/client/src/assets/player/settings-menu-item.ts +++ b/client/src/assets/player/settings-menu-item.ts | |||
@@ -38,8 +38,11 @@ class SettingsMenuItem extends MenuItem { | |||
38 | this.eventHandlers() | 38 | this.eventHandlers() |
39 | 39 | ||
40 | player.ready(() => { | 40 | player.ready(() => { |
41 | this.build() | 41 | // Voodoo magic for IOS |
42 | this.reset() | 42 | setTimeout(() => { |
43 | this.build() | ||
44 | this.reset() | ||
45 | }, 0) | ||
43 | }) | 46 | }) |
44 | } | 47 | } |
45 | 48 | ||
diff --git a/client/src/hmr.ts b/client/src/hmr.ts index 4d707a250..d5306a7a2 100644 --- a/client/src/hmr.ts +++ b/client/src/hmr.ts | |||
@@ -1,11 +1,19 @@ | |||
1 | import { NgModuleRef, ApplicationRef } from '@angular/core' | 1 | import { NgModuleRef, ApplicationRef } from '@angular/core' |
2 | import { createNewHosts } from '@angularclass/hmr' | 2 | import { createNewHosts } from '@angularclass/hmr' |
3 | import { enableDebugTools } from '@angular/platform-browser' | ||
3 | 4 | ||
4 | export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef<any>>) => { | 5 | export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef<any>>) => { |
5 | let ngModule: NgModuleRef<any> | 6 | let ngModule: NgModuleRef<any> |
6 | module.hot.accept() | 7 | module.hot.accept() |
7 | bootstrap() | 8 | bootstrap() |
8 | .then(mod => ngModule = mod) | 9 | .then(mod => { |
10 | ngModule = mod | ||
11 | |||
12 | const applicationRef = ngModule.injector.get(ApplicationRef); | ||
13 | const componentRef = applicationRef.components[ 0 ] | ||
14 | // allows to run `ng.profiler.timeChangeDetection();` | ||
15 | enableDebugTools(componentRef) | ||
16 | }) | ||
9 | module.hot.dispose(() => { | 17 | module.hot.dispose(() => { |
10 | const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef) | 18 | const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef) |
11 | const elements = appRef.components.map(c => c.location.nativeElement) | 19 | const elements = appRef.components.map(c => c.location.nativeElement) |
diff --git a/client/src/index.html b/client/src/index.html index f00af8bff..593de4ac6 100644 --- a/client/src/index.html +++ b/client/src/index.html | |||
@@ -7,7 +7,7 @@ | |||
7 | <meta name="theme-color" content="#fff" /> | 7 | <meta name="theme-color" content="#fff" /> |
8 | 8 | ||
9 | <!-- Web Manifest file --> | 9 | <!-- Web Manifest file --> |
10 | <link rel="manifest" href="/manifest.json"> | 10 | <link rel="manifest" href="/manifest.webmanifest"> |
11 | 11 | ||
12 | <!-- /!\ The following comment is used by the server to prerender some tags /!\ --> | 12 | <!-- /!\ The following comment is used by the server to prerender some tags /!\ --> |
13 | 13 | ||
diff --git a/client/src/manifest.json b/client/src/manifest.webmanifest index 30914e35f..3d3c7d6d5 100644 --- a/client/src/manifest.json +++ b/client/src/manifest.webmanifest | |||
@@ -24,7 +24,7 @@ | |||
24 | "src": "/client/assets/images/icons/icon-96x96.png", | 24 | "src": "/client/assets/images/icons/icon-96x96.png", |
25 | "sizes": "96x96", | 25 | "sizes": "96x96", |
26 | "type": "image/png" | 26 | "type": "image/png" |
27 | }, | 27 | }, |
28 | { | 28 | { |
29 | "src": "/client/assets/images/icons/icon-144x144.png", | 29 | "src": "/client/assets/images/icons/icon-144x144.png", |
30 | "sizes": "144x144", | 30 | "sizes": "144x144", |
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index caf039b6d..f21b91d2e 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss | |||
@@ -9,7 +9,7 @@ $icon-font-path: '../../node_modules/@neos21/bootstrap3-glyphicons/assets/fonts/ | |||
9 | @import '~video.js/dist/video-js.css'; | 9 | @import '~video.js/dist/video-js.css'; |
10 | 10 | ||
11 | $assets-path: '../assets/'; | 11 | $assets-path: '../assets/'; |
12 | @import './player/player'; | 12 | @import './player/index'; |
13 | @import './loading-bar'; | 13 | @import './loading-bar'; |
14 | 14 | ||
15 | @import './primeng-custom'; | 15 | @import './primeng-custom'; |
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index d755e7df3..544f39957 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss | |||
@@ -53,7 +53,6 @@ | |||
53 | -ms-hyphens: auto; | 53 | -ms-hyphens: auto; |
54 | -moz-hyphens: auto; | 54 | -moz-hyphens: auto; |
55 | hyphens: auto; | 55 | hyphens: auto; |
56 | text-align: justify; | ||
57 | } | 56 | } |
58 | 57 | ||
59 | @mixin peertube-input-text($width) { | 58 | @mixin peertube-input-text($width) { |
diff --git a/client/src/sass/player/player.scss b/client/src/sass/player/index.scss index e4a315d1f..e4a315d1f 100644 --- a/client/src/sass/player/player.scss +++ b/client/src/sass/player/index.scss | |||
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss index 185b00222..4e921e970 100644 --- a/client/src/sass/player/peertube-skin.scss +++ b/client/src/sass/player/peertube-skin.scss | |||
@@ -406,6 +406,7 @@ | |||
406 | 406 | ||
407 | width: 37px; | 407 | width: 37px; |
408 | margin-right: 1px; | 408 | margin-right: 1px; |
409 | cursor: pointer; | ||
409 | 410 | ||
410 | .vjs-icon-placeholder { | 411 | .vjs-icon-placeholder { |
411 | transition: transform 0.2s ease; | 412 | transition: transform 0.2s ease; |
@@ -504,10 +505,6 @@ | |||
504 | } | 505 | } |
505 | } | 506 | } |
506 | 507 | ||
507 | .vjs-playback-rate { | ||
508 | display: none; | ||
509 | } | ||
510 | |||
511 | .vjs-peertube { | 508 | .vjs-peertube { |
512 | padding: 0 !important; | 509 | padding: 0 !important; |
513 | 510 | ||
diff --git a/client/src/standalone/videos/embed.scss b/client/src/standalone/videos/embed.scss index 30650538f..c40ea1208 100644 --- a/client/src/standalone/videos/embed.scss +++ b/client/src/standalone/videos/embed.scss | |||
@@ -4,7 +4,7 @@ | |||
4 | @import '~videojs-dock/dist/videojs-dock.css'; | 4 | @import '~videojs-dock/dist/videojs-dock.css'; |
5 | 5 | ||
6 | $assets-path: '../../assets/'; | 6 | $assets-path: '../../assets/'; |
7 | @import '../../sass/player/player'; | 7 | @import '../../sass/player/index'; |
8 | 8 | ||
9 | [hidden] { | 9 | [hidden] { |
10 | display: none !important; | 10 | display: none !important; |