aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-06-12 20:04:58 +0200
committerChocobozzz <me@florianbigard.com>2018-06-12 20:37:51 +0200
commit2186386cca113506791583cb07d6ccacba7af4e0 (patch)
tree3c214c0b5fbd64332624267fa6e51fd4a9cf6474 /client/src/app
parent6ccdf3a23ecec5ba2eeaf487fd1fafdc7606b4bf (diff)
downloadPeerTube-2186386cca113506791583cb07d6ccacba7af4e0.tar.gz
PeerTube-2186386cca113506791583cb07d6ccacba7af4e0.tar.zst
PeerTube-2186386cca113506791583cb07d6ccacba7af4e0.zip
Add concept of video state, and add ability to wait transcoding before
publishing a video
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.html2
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.ts60
-rw-r--r--client/src/app/shared/video/video-details.model.ts12
-rw-r--r--client/src/app/shared/video/video-edit.model.ts8
-rw-r--r--client/src/app/shared/video/video.model.ts14
-rw-r--r--client/src/app/shared/video/video.service.ts39
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.html10
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.ts8
-rw-r--r--client/src/app/videos/+video-edit/video-add.component.ts2
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html4
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.scss4
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts147
12 files changed, 189 insertions, 121 deletions
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
index 35a99d0b3..eb24de7a7 100644
--- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
+++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
@@ -18,7 +18,7 @@
18 <div class="video-info"> 18 <div class="video-info">
19 <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> 19 <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
20 <span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span> 20 <span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
21 <div class="video-info-private">{{ video.privacy.label }}</div> 21 <div class="video-info-private">{{ video.privacy.label }} - {{ getStateLabel(video) }}</div>
22 </div> 22 </div>
23 23
24 <!-- Display only once --> 24 <!-- Display only once -->
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
index eed4be01f..afc01073c 100644
--- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
+++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
@@ -12,6 +12,7 @@ import { AbstractVideoList } from '../../shared/video/abstract-video-list'
12import { Video } from '../../shared/video/video.model' 12import { Video } from '../../shared/video/video.model'
13import { VideoService } from '../../shared/video/video.service' 13import { VideoService } from '../../shared/video/video.service'
14import { I18n } from '@ngx-translate/i18n-polyfill' 14import { I18n } from '@ngx-translate/i18n-polyfill'
15import { VideoState } from '../../../../../shared/models/videos'
15 16
16@Component({ 17@Component({
17 selector: 'my-account-videos', 18 selector: 'my-account-videos',
@@ -59,7 +60,7 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
59 } 60 }
60 61
61 isInSelectionMode () { 62 isInSelectionMode () {
62 return Object.keys(this.checkedVideos).some(k => this.checkedVideos[k] === true) 63 return Object.keys(this.checkedVideos).some(k => this.checkedVideos[ k ] === true)
63 } 64 }
64 65
65 getVideosObservable (page: number) { 66 getVideosObservable (page: number) {
@@ -74,47 +75,68 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
74 75
75 async deleteSelectedVideos () { 76 async deleteSelectedVideos () {
76 const toDeleteVideosIds = Object.keys(this.checkedVideos) 77 const toDeleteVideosIds = Object.keys(this.checkedVideos)
77 .filter(k => this.checkedVideos[k] === true) 78 .filter(k => this.checkedVideos[ k ] === true)
78 .map(k => parseInt(k, 10)) 79 .map(k => parseInt(k, 10))
79 80
80 const res = await this.confirmService.confirm(`Do you really want to delete ${toDeleteVideosIds.length} videos?`, 'Delete') 81 const res = await this.confirmService.confirm(
82 this.i18n('Do you really want to delete {{deleteLength}} videos?', { deleteLength: toDeleteVideosIds.length }),
83 this.i18n('Delete')
84 )
81 if (res === false) return 85 if (res === false) return
82 86
83 const observables: Observable<any>[] = [] 87 const observables: Observable<any>[] = []
84 for (const videoId of toDeleteVideosIds) { 88 for (const videoId of toDeleteVideosIds) {
85 const o = this.videoService 89 const o = this.videoService.removeVideo(videoId)
86 .removeVideo(videoId)
87 .pipe(tap(() => this.spliceVideosById(videoId))) 90 .pipe(tap(() => this.spliceVideosById(videoId)))
88 91
89 observables.push(o) 92 observables.push(o)
90 } 93 }
91 94
92 observableFrom(observables).pipe( 95 observableFrom(observables)
93 concatAll()) 96 .pipe(concatAll())
94 .subscribe( 97 .subscribe(
95 res => { 98 res => {
96 this.notificationsService.success('Success', `${toDeleteVideosIds.length} videos deleted.`) 99 this.notificationsService.success(
100 this.i18n('Success'),
101 this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length })
102 )
103
97 this.abortSelectionMode() 104 this.abortSelectionMode()
98 this.reloadVideos() 105 this.reloadVideos()
99 }, 106 },
100 107
101 err => this.notificationsService.error('Error', err.message) 108 err => this.notificationsService.error(this.i18n('Error'), err.message)
102 ) 109 )
103 } 110 }
104 111
105 async deleteVideo (video: Video) { 112 async deleteVideo (video: Video) {
106 const res = await this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete') 113 const res = await this.confirmService.confirm(
114 this.i18n('Do you really want to delete {{videoName}}?', { videoName: video.name }),
115 this.i18n('Delete')
116 )
107 if (res === false) return 117 if (res === false) return
108 118
109 this.videoService.removeVideo(video.id) 119 this.videoService.removeVideo(video.id)
110 .subscribe( 120 .subscribe(
111 status => { 121 status => {
112 this.notificationsService.success('Success', `Video ${video.name} deleted.`) 122 this.notificationsService.success(
113 this.reloadVideos() 123 this.i18n('Success'),
114 }, 124 this.i18n('Video {{videoName}} deleted.', { videoName: video.name })
125 )
126 this.reloadVideos()
127 },
128
129 error => this.notificationsService.error(this.i18n('Error'), error.message)
130 )
131 }
115 132
116 error => this.notificationsService.error('Error', error.message) 133 getStateLabel (video: Video) {
117 ) 134 if (video.state.id === VideoState.PUBLISHED) return this.i18n('Published')
135
136 if (video.state.id === VideoState.TO_TRANSCODE && video.waitTranscoding === true) return this.i18n('Waiting transcoding')
137 if (video.state.id === VideoState.TO_TRANSCODE) return this.i18n('To transcode')
138
139 return this.i18n('Unknown state')
118 } 140 }
119 141
120 protected buildVideoHeight () { 142 protected buildVideoHeight () {
@@ -124,7 +146,7 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
124 146
125 private spliceVideosById (id: number) { 147 private spliceVideosById (id: number) {
126 for (const key of Object.keys(this.loadedPages)) { 148 for (const key of Object.keys(this.loadedPages)) {
127 const videos = this.loadedPages[key] 149 const videos = this.loadedPages[ key ]
128 const index = videos.findIndex(v => v.id === id) 150 const index = videos.findIndex(v => v.id === id)
129 151
130 if (index !== -1) { 152 if (index !== -1) {
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts
index 19c350ab3..e500ad6fc 100644
--- a/client/src/app/shared/video/video-details.model.ts
+++ b/client/src/app/shared/video/video-details.model.ts
@@ -1,4 +1,11 @@
1import { UserRight, VideoChannel, VideoDetails as VideoDetailsServerModel, VideoFile } from '../../../../../shared' 1import {
2 UserRight,
3 VideoChannel,
4 VideoConstant,
5 VideoDetails as VideoDetailsServerModel,
6 VideoFile,
7 VideoState
8} from '../../../../../shared'
2import { AuthUser } from '../../core' 9import { AuthUser } from '../../core'
3import { Video } from '../../shared/video/video.model' 10import { Video } from '../../shared/video/video.model'
4import { Account } from '@app/shared/account/account.model' 11import { Account } from '@app/shared/account/account.model'
@@ -12,6 +19,9 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
12 account: Account 19 account: Account
13 commentsEnabled: boolean 20 commentsEnabled: boolean
14 21
22 waitTranscoding: boolean
23 state: VideoConstant<VideoState>
24
15 likesPercent: number 25 likesPercent: number
16 dislikesPercent: number 26 dislikesPercent: number
17 27
diff --git a/client/src/app/shared/video/video-edit.model.ts b/client/src/app/shared/video/video-edit.model.ts
index ad2929db5..f045a3acd 100644
--- a/client/src/app/shared/video/video-edit.model.ts
+++ b/client/src/app/shared/video/video-edit.model.ts
@@ -1,7 +1,8 @@
1import { VideoDetails } from './video-details.model' 1import { VideoDetails } from './video-details.model'
2import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum' 2import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
3import { VideoUpdate } from '../../../../../shared/models/videos'
3 4
4export class VideoEdit { 5export class VideoEdit implements VideoUpdate {
5 category: number 6 category: number
6 licence: number 7 licence: number
7 language: string 8 language: string
@@ -10,6 +11,7 @@ export class VideoEdit {
10 tags: string[] 11 tags: string[]
11 nsfw: boolean 12 nsfw: boolean
12 commentsEnabled: boolean 13 commentsEnabled: boolean
14 waitTranscoding: boolean
13 channelId: number 15 channelId: number
14 privacy: VideoPrivacy 16 privacy: VideoPrivacy
15 support: string 17 support: string
@@ -32,6 +34,7 @@ export class VideoEdit {
32 this.tags = videoDetails.tags 34 this.tags = videoDetails.tags
33 this.nsfw = videoDetails.nsfw 35 this.nsfw = videoDetails.nsfw
34 this.commentsEnabled = videoDetails.commentsEnabled 36 this.commentsEnabled = videoDetails.commentsEnabled
37 this.waitTranscoding = videoDetails.waitTranscoding
35 this.channelId = videoDetails.channel.id 38 this.channelId = videoDetails.channel.id
36 this.privacy = videoDetails.privacy.id 39 this.privacy = videoDetails.privacy.id
37 this.support = videoDetails.support 40 this.support = videoDetails.support
@@ -42,7 +45,7 @@ export class VideoEdit {
42 45
43 patch (values: Object) { 46 patch (values: Object) {
44 Object.keys(values).forEach((key) => { 47 Object.keys(values).forEach((key) => {
45 this[key] = values[key] 48 this[ key ] = values[ key ]
46 }) 49 })
47 } 50 }
48 51
@@ -57,6 +60,7 @@ export class VideoEdit {
57 tags: this.tags, 60 tags: this.tags,
58 nsfw: this.nsfw, 61 nsfw: this.nsfw,
59 commentsEnabled: this.commentsEnabled, 62 commentsEnabled: this.commentsEnabled,
63 waitTranscoding: this.waitTranscoding,
60 channelId: this.channelId, 64 channelId: this.channelId,
61 privacy: this.privacy 65 privacy: this.privacy
62 } 66 }
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
index d37dc2c3e..48a4b4260 100644
--- a/client/src/app/shared/video/video.model.ts
+++ b/client/src/app/shared/video/video.model.ts
@@ -1,5 +1,5 @@
1import { User } from '../' 1import { User } from '../'
2import { Video as VideoServerModel, VideoPrivacy } from '../../../../../shared' 2import { Video as VideoServerModel, VideoPrivacy, VideoState } from '../../../../../shared'
3import { Avatar } from '../../../../../shared/models/avatars/avatar.model' 3import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
4import { VideoConstant } from '../../../../../shared/models/videos/video.model' 4import { VideoConstant } from '../../../../../shared/models/videos/video.model'
5import { getAbsoluteAPIUrl } from '../misc/utils' 5import { getAbsoluteAPIUrl } from '../misc/utils'
@@ -36,6 +36,9 @@ export class Video implements VideoServerModel {
36 dislikes: number 36 dislikes: number
37 nsfw: boolean 37 nsfw: boolean
38 38
39 waitTranscoding?: boolean
40 state?: VideoConstant<VideoState>
41
39 account: { 42 account: {
40 id: number 43 id: number
41 uuid: string 44 uuid: string
@@ -58,15 +61,14 @@ export class Video implements VideoServerModel {
58 61
59 private static createDurationString (duration: number) { 62 private static createDurationString (duration: number) {
60 const hours = Math.floor(duration / 3600) 63 const hours = Math.floor(duration / 3600)
61 const minutes = Math.floor(duration % 3600 / 60) 64 const minutes = Math.floor((duration % 3600) / 60)
62 const seconds = duration % 60 65 const seconds = duration % 60
63 66
64 const minutesPadding = minutes >= 10 ? '' : '0' 67 const minutesPadding = minutes >= 10 ? '' : '0'
65 const secondsPadding = seconds >= 10 ? '' : '0' 68 const secondsPadding = seconds >= 10 ? '' : '0'
66 const displayedHours = hours > 0 ? hours.toString() + ':' : '' 69 const displayedHours = hours > 0 ? hours.toString() + ':' : ''
67 70
68 return displayedHours + minutesPadding + 71 return displayedHours + minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString()
69 minutes.toString() + ':' + secondsPadding + seconds.toString()
70 } 72 }
71 73
72 constructor (hash: VideoServerModel, translations = {}) { 74 constructor (hash: VideoServerModel, translations = {}) {
@@ -78,6 +80,8 @@ export class Video implements VideoServerModel {
78 this.licence = hash.licence 80 this.licence = hash.licence
79 this.language = hash.language 81 this.language = hash.language
80 this.privacy = hash.privacy 82 this.privacy = hash.privacy
83 this.waitTranscoding = hash.waitTranscoding
84 this.state = hash.state
81 this.description = hash.description 85 this.description = hash.description
82 this.duration = hash.duration 86 this.duration = hash.duration
83 this.durationLabel = Video.createDurationString(hash.duration) 87 this.durationLabel = Video.createDurationString(hash.duration)
@@ -104,6 +108,8 @@ export class Video implements VideoServerModel {
104 this.licence.label = peertubeTranslate(this.licence.label, translations) 108 this.licence.label = peertubeTranslate(this.licence.label, translations)
105 this.language.label = peertubeTranslate(this.language.label, translations) 109 this.language.label = peertubeTranslate(this.language.label, translations)
106 this.privacy.label = peertubeTranslate(this.privacy.label, translations) 110 this.privacy.label = peertubeTranslate(this.privacy.label, translations)
111
112 if (this.state) this.state.label = peertubeTranslate(this.state.label, translations)
107 } 113 }
108 114
109 isVideoNSFWForUser (user: User, serverConfig: ServerConfig) { 115 isVideoNSFWForUser (user: User, serverConfig: ServerConfig) {
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index 58cb52efc..d63915ad2 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -80,6 +80,7 @@ export class VideoService {
80 privacy: video.privacy, 80 privacy: video.privacy,
81 tags: video.tags, 81 tags: video.tags,
82 nsfw: video.nsfw, 82 nsfw: video.nsfw,
83 waitTranscoding: video.waitTranscoding,
83 commentsEnabled: video.commentsEnabled, 84 commentsEnabled: video.commentsEnabled,
84 thumbnailfile: video.thumbnailfile, 85 thumbnailfile: video.thumbnailfile,
85 previewfile: video.previewfile 86 previewfile: video.previewfile
@@ -98,11 +99,11 @@ export class VideoService {
98 const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true }) 99 const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true })
99 100
100 return this.authHttp 101 return this.authHttp
101 .request<{ video: { id: number, uuid: string} }>(req) 102 .request<{ video: { id: number, uuid: string } }>(req)
102 .pipe(catchError(this.restExtractor.handleError)) 103 .pipe(catchError(this.restExtractor.handleError))
103 } 104 }
104 105
105 getMyVideos (videoPagination: ComponentPagination, sort: VideoSortField): Observable<{ videos: Video[], totalVideos: number}> { 106 getMyVideos (videoPagination: ComponentPagination, sort: VideoSortField): Observable<{ videos: Video[], totalVideos: number }> {
106 const pagination = this.restService.componentPaginationToRestPagination(videoPagination) 107 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
107 108
108 let params = new HttpParams() 109 let params = new HttpParams()
@@ -120,7 +121,7 @@ export class VideoService {
120 account: Account, 121 account: Account,
121 videoPagination: ComponentPagination, 122 videoPagination: ComponentPagination,
122 sort: VideoSortField 123 sort: VideoSortField
123 ): Observable<{ videos: Video[], totalVideos: number}> { 124 ): Observable<{ videos: Video[], totalVideos: number }> {
124 const pagination = this.restService.componentPaginationToRestPagination(videoPagination) 125 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
125 126
126 let params = new HttpParams() 127 let params = new HttpParams()
@@ -138,7 +139,7 @@ export class VideoService {
138 videoChannel: VideoChannel, 139 videoChannel: VideoChannel,
139 videoPagination: ComponentPagination, 140 videoPagination: ComponentPagination,
140 sort: VideoSortField 141 sort: VideoSortField
141 ): Observable<{ videos: Video[], totalVideos: number}> { 142 ): Observable<{ videos: Video[], totalVideos: number }> {
142 const pagination = this.restService.componentPaginationToRestPagination(videoPagination) 143 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
143 144
144 let params = new HttpParams() 145 let params = new HttpParams()
@@ -156,7 +157,7 @@ export class VideoService {
156 videoPagination: ComponentPagination, 157 videoPagination: ComponentPagination,
157 sort: VideoSortField, 158 sort: VideoSortField,
158 filter?: VideoFilter 159 filter?: VideoFilter
159 ): Observable<{ videos: Video[], totalVideos: number}> { 160 ): Observable<{ videos: Video[], totalVideos: number }> {
160 const pagination = this.restService.componentPaginationToRestPagination(videoPagination) 161 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
161 162
162 let params = new HttpParams() 163 let params = new HttpParams()
@@ -225,7 +226,7 @@ export class VideoService {
225 search: string, 226 search: string,
226 videoPagination: ComponentPagination, 227 videoPagination: ComponentPagination,
227 sort: VideoSortField 228 sort: VideoSortField
228 ): Observable<{ videos: Video[], totalVideos: number}> { 229 ): Observable<{ videos: Video[], totalVideos: number }> {
229 const url = VideoService.BASE_VIDEO_URL + 'search' 230 const url = VideoService.BASE_VIDEO_URL + 'search'
230 231
231 const pagination = this.restService.componentPaginationToRestPagination(videoPagination) 232 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
@@ -295,18 +296,18 @@ export class VideoService {
295 296
296 private extractVideos (result: ResultList<VideoServerModel>) { 297 private extractVideos (result: ResultList<VideoServerModel>) {
297 return this.serverService.localeObservable 298 return this.serverService.localeObservable
298 .pipe( 299 .pipe(
299 map(translations => { 300 map(translations => {
300 const videosJson = result.data 301 const videosJson = result.data
301 const totalVideos = result.total 302 const totalVideos = result.total
302 const videos: Video[] = [] 303 const videos: Video[] = []
303 304
304 for (const videoJson of videosJson) { 305 for (const videoJson of videosJson) {
305 videos.push(new Video(videoJson, translations)) 306 videos.push(new Video(videoJson, translations))
306 } 307 }
307 308
308 return { videos, totalVideos } 309 return { videos, totalVideos }
309 }) 310 })
310 ) 311 )
311 } 312 }
312} 313}
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html
index c8cd0d679..379cf7948 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.html
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html
@@ -109,6 +109,16 @@
109 <label i18n for="commentsEnabled">Enable video comments</label> 109 <label i18n for="commentsEnabled">Enable video comments</label>
110 </div> 110 </div>
111 111
112 <div class="form-group form-group-checkbox">
113 <input type="checkbox" id="waitTranscoding" formControlName="waitTranscoding" />
114 <label for="waitTranscoding"></label>
115 <label i18n for="waitTranscoding">Wait transcoding before publishing the video</label>
116 <my-help
117 tooltipPlacement="top" helpType="custom" i18n-customHtml
118 customHtml="If you decide to not wait transcoding before publishing the video, it can be unplayable until it transcoding ends."
119 ></my-help>
120 </div>
121
112 </div> 122 </div>
113 </tab> 123 </tab>
114 124
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.ts b/client/src/app/videos/+video-edit/shared/video-edit.component.ts
index 61515c0b0..ee4fd5dc1 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.ts
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.ts
@@ -47,6 +47,7 @@ export class VideoEditComponent implements OnInit {
47 const defaultValues = { 47 const defaultValues = {
48 nsfw: 'false', 48 nsfw: 'false',
49 commentsEnabled: 'true', 49 commentsEnabled: 'true',
50 waitTranscoding: 'true',
50 tags: [] 51 tags: []
51 } 52 }
52 const obj = { 53 const obj = {
@@ -55,6 +56,7 @@ export class VideoEditComponent implements OnInit {
55 channelId: this.videoValidatorsService.VIDEO_CHANNEL, 56 channelId: this.videoValidatorsService.VIDEO_CHANNEL,
56 nsfw: null, 57 nsfw: null,
57 commentsEnabled: null, 58 commentsEnabled: null,
59 waitTranscoding: null,
58 category: this.videoValidatorsService.VIDEO_CATEGORY, 60 category: this.videoValidatorsService.VIDEO_CATEGORY,
59 licence: this.videoValidatorsService.VIDEO_LICENCE, 61 licence: this.videoValidatorsService.VIDEO_LICENCE,
60 language: this.videoValidatorsService.VIDEO_LANGUAGE, 62 language: this.videoValidatorsService.VIDEO_LANGUAGE,
@@ -74,13 +76,13 @@ export class VideoEditComponent implements OnInit {
74 ) 76 )
75 77
76 // We will update the "support" field depending on the channel 78 // We will update the "support" field depending on the channel
77 this.form.controls['channelId'] 79 this.form.controls[ 'channelId' ]
78 .valueChanges 80 .valueChanges
79 .pipe(map(res => parseInt(res.toString(), 10))) 81 .pipe(map(res => parseInt(res.toString(), 10)))
80 .subscribe( 82 .subscribe(
81 newChannelId => { 83 newChannelId => {
82 const oldChannelId = parseInt(this.form.value['channelId'], 10) 84 const oldChannelId = parseInt(this.form.value[ 'channelId' ], 10)
83 const currentSupport = this.form.value['support'] 85 const currentSupport = this.form.value[ 'support' ]
84 86
85 // Not initialized yet 87 // Not initialized yet
86 if (isNaN(newChannelId)) return 88 if (isNaN(newChannelId)) return
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts
index 332f757d7..85afd0caa 100644
--- a/client/src/app/videos/+video-edit/video-add.component.ts
+++ b/client/src/app/videos/+video-edit/video-add.component.ts
@@ -164,6 +164,7 @@ export class VideoAddComponent extends FormReactive implements OnInit, OnDestroy
164 164
165 const privacy = this.firstStepPrivacyId.toString() 165 const privacy = this.firstStepPrivacyId.toString()
166 const nsfw = false 166 const nsfw = false
167 const waitTranscoding = true
167 const commentsEnabled = true 168 const commentsEnabled = true
168 const channelId = this.firstStepChannelId.toString() 169 const channelId = this.firstStepChannelId.toString()
169 170
@@ -173,6 +174,7 @@ export class VideoAddComponent extends FormReactive implements OnInit, OnDestroy
173 formData.append('privacy', VideoPrivacy.PRIVATE.toString()) 174 formData.append('privacy', VideoPrivacy.PRIVATE.toString())
174 formData.append('nsfw', '' + nsfw) 175 formData.append('nsfw', '' + nsfw)
175 formData.append('commentsEnabled', '' + commentsEnabled) 176 formData.append('commentsEnabled', '' + commentsEnabled)
177 formData.append('waitTranscoding', '' + waitTranscoding)
176 formData.append('channelId', '' + channelId) 178 formData.append('channelId', '' + channelId)
177 formData.append('videofile', videofile) 179 formData.append('videofile', videofile)
178 180
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 4c650b121..8bd5c00ff 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -3,6 +3,10 @@
3 <div id="video-element-wrapper"> 3 <div id="video-element-wrapper">
4 </div> 4 </div>
5 5
6 <div i18n id="warning-transcoding" class="alert alert-warning" *ngIf="isVideoToTranscode()">
7 The video is being transcoded, it may not work properly.
8 </div>
9
6 <!-- Video information --> 10 <!-- Video information -->
7 <div *ngIf="video" class="margin-content video-bottom"> 11 <div *ngIf="video" class="margin-content video-bottom">
8 <div class="video-info"> 12 <div class="video-info">
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 00e776a69..06dd75653 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -28,6 +28,10 @@
28 } 28 }
29} 29}
30 30
31#warning-transcoding {
32 text-align: center;
33}
34
31#video-not-found { 35#video-not-found {
32 height: 300px; 36 height: 300px;
33 line-height: 300px; 37 line-height: 300px;
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 eefa43a73..498542fff 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -1,5 +1,5 @@
1import { catchError } from 'rxjs/operators' 1import { catchError } from 'rxjs/operators'
2import { Component, ElementRef, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild, Inject } from '@angular/core' 2import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { RedirectService } from '@app/core/routing/redirect.service' 4import { RedirectService } from '@app/core/routing/redirect.service'
5import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' 5import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
@@ -10,7 +10,7 @@ import { Subscription } from 'rxjs'
10import * as videojs from 'video.js' 10import * as videojs from 'video.js'
11import 'videojs-hotkeys' 11import 'videojs-hotkeys'
12import * as WebTorrent from 'webtorrent' 12import * as WebTorrent from 'webtorrent'
13import { UserVideoRateType, VideoRateType } from '../../../../../shared' 13import { UserVideoRateType, VideoRateType, VideoState } from '../../../../../shared'
14import '../../../assets/player/peertube-videojs-plugin' 14import '../../../assets/player/peertube-videojs-plugin'
15import { AuthService, ConfirmService } from '../../core' 15import { AuthService, ConfirmService } from '../../core'
16import { RestExtractor, VideoBlacklistService } from '../../shared' 16import { RestExtractor, VideoBlacklistService } from '../../shared'
@@ -21,7 +21,7 @@ import { MarkdownService } from '../shared'
21import { VideoDownloadComponent } from './modal/video-download.component' 21import { VideoDownloadComponent } from './modal/video-download.component'
22import { VideoReportComponent } from './modal/video-report.component' 22import { VideoReportComponent } from './modal/video-report.component'
23import { VideoShareComponent } from './modal/video-share.component' 23import { VideoShareComponent } from './modal/video-share.component'
24import { getVideojsOptions, loadLocale, addContextMenu } from '../../../assets/player/peertube-player' 24import { addContextMenu, getVideojsOptions, loadLocale } from '../../../assets/player/peertube-player'
25import { ServerService } from '@app/core' 25import { ServerService } from '@app/core'
26import { I18n } from '@ngx-translate/i18n-polyfill' 26import { I18n } from '@ngx-translate/i18n-polyfill'
27import { environment } from '../../../environments/environment' 27import { environment } from '../../../environments/environment'
@@ -91,21 +91,21 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
91 } 91 }
92 92
93 this.videoService.getVideos({ currentPage: 1, itemsPerPage: 5 }, '-createdAt') 93 this.videoService.getVideos({ currentPage: 1, itemsPerPage: 5 }, '-createdAt')
94 .subscribe( 94 .subscribe(
95 data => { 95 data => {
96 this.otherVideos = data.videos 96 this.otherVideos = data.videos
97 this.updateOtherVideosDisplayed() 97 this.updateOtherVideosDisplayed()
98 }, 98 },
99 99
100 err => console.error(err) 100 err => console.error(err)
101 ) 101 )
102 102
103 this.paramsSub = this.route.params.subscribe(routeParams => { 103 this.paramsSub = this.route.params.subscribe(routeParams => {
104 if (this.player) { 104 if (this.player) {
105 this.player.pause() 105 this.player.pause()
106 } 106 }
107 107
108 const uuid = routeParams['uuid'] 108 const uuid = routeParams[ 'uuid' ]
109 109
110 // Video did not change 110 // Video did not change
111 if (this.video && this.video.uuid === uuid) return 111 if (this.video && this.video.uuid === uuid) return
@@ -113,13 +113,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
113 this.videoService 113 this.videoService
114 .getVideo(uuid) 114 .getVideo(uuid)
115 .pipe(catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ]))) 115 .pipe(catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ])))
116 .subscribe( 116 .subscribe(video => {
117 video => { 117 const startTime = this.route.snapshot.queryParams.start
118 const startTime = this.route.snapshot.queryParams.start 118 this.onVideoFetched(video, startTime)
119 this.onVideoFetched(video, startTime) 119 .catch(err => this.handleError(err))
120 .catch(err => this.handleError(err)) 120 })
121 }
122 )
123 }) 121 })
124 } 122 }
125 123
@@ -157,17 +155,17 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
157 if (res === false) return 155 if (res === false) return
158 156
159 this.videoBlacklistService.blacklistVideo(this.video.id) 157 this.videoBlacklistService.blacklistVideo(this.video.id)
160 .subscribe( 158 .subscribe(
161 status => { 159 () => {
162 this.notificationsService.success( 160 this.notificationsService.success(
163 this.i18n('Success'), 161 this.i18n('Success'),
164 this.i18n('Video {{videoName}} had been blacklisted.', { videoName: this.video.name }) 162 this.i18n('Video {{videoName}} had been blacklisted.', { videoName: this.video.name })
165 ) 163 )
166 this.redirectService.redirectToHomepage() 164 this.redirectService.redirectToHomepage()
167 }, 165 },
168 166
169 error => this.notificationsService.error(this.i18n('Error'), error.message) 167 error => this.notificationsService.error(this.i18n('Error'), error.message)
170 ) 168 )
171 } 169 }
172 170
173 showMoreDescription () { 171 showMoreDescription () {
@@ -188,22 +186,22 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
188 this.descriptionLoading = true 186 this.descriptionLoading = true
189 187
190 this.videoService.loadCompleteDescription(this.video.descriptionPath) 188 this.videoService.loadCompleteDescription(this.video.descriptionPath)
191 .subscribe( 189 .subscribe(
192 description => { 190 description => {
193 this.completeDescriptionShown = true 191 this.completeDescriptionShown = true
194 this.descriptionLoading = false 192 this.descriptionLoading = false
195 193
196 this.shortVideoDescription = this.video.description 194 this.shortVideoDescription = this.video.description
197 this.completeVideoDescription = description 195 this.completeVideoDescription = description
198 196
199 this.updateVideoDescription(this.completeVideoDescription) 197 this.updateVideoDescription(this.completeVideoDescription)
200 }, 198 },
201 199
202 error => { 200 error => {
203 this.descriptionLoading = false 201 this.descriptionLoading = false
204 this.notificationsService.error(this.i18n('Error'), error.message) 202 this.notificationsService.error(this.i18n('Error'), error.message)
205 } 203 }
206 ) 204 )
207 } 205 }
208 206
209 showReportModal (event: Event) { 207 showReportModal (event: Event) {
@@ -259,19 +257,19 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
259 if (res === false) return 257 if (res === false) return
260 258
261 this.videoService.removeVideo(this.video.id) 259 this.videoService.removeVideo(this.video.id)
262 .subscribe( 260 .subscribe(
263 status => { 261 status => {
264 this.notificationsService.success( 262 this.notificationsService.success(
265 this.i18n('Success'), 263 this.i18n('Success'),
266 this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name }) 264 this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name })
267 ) 265 )
268 266
269 // Go back to the video-list. 267 // Go back to the video-list.
270 this.redirectService.redirectToHomepage() 268 this.redirectService.redirectToHomepage()
271 }, 269 },
272 270
273 error => this.notificationsService.error(this.i18n('Error'), error.message) 271 error => this.notificationsService.error(this.i18n('Error'), error.message)
274 ) 272 )
275 } 273 }
276 274
277 acceptedPrivacyConcern () { 275 acceptedPrivacyConcern () {
@@ -279,6 +277,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
279 this.hasAlreadyAcceptedPrivacyConcern = true 277 this.hasAlreadyAcceptedPrivacyConcern = true
280 } 278 }
281 279
280 isVideoToTranscode () {
281 return this.video && this.video.state.id === VideoState.TO_TRANSCODE
282 }
283
282 private updateVideoDescription (description: string) { 284 private updateVideoDescription (description: string) {
283 this.video.description = description 285 this.video.description = description
284 this.setVideoDescriptionHTML() 286 this.setVideoDescriptionHTML()
@@ -294,10 +296,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
294 } 296 }
295 297
296 private setVideoLikesBarTooltipText () { 298 private setVideoLikesBarTooltipText () {
297 this.likesBarTooltipText = this.i18n( 299 this.likesBarTooltipText = this.i18n('{{likesNumber}} likes / {{dislikesNumber}} dislikes', {
298 '{{likesNumber}} likes / {{dislikesNumber}} dislikes', 300 likesNumber: this.video.likes,
299 { likesNumber: this.video.likes, dislikesNumber: this.video.dislikes } 301 dislikesNumber: this.video.dislikes
300 ) 302 })
301 } 303 }
302 304
303 private handleError (err: any) { 305 private handleError (err: any) {
@@ -320,15 +322,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
320 if (this.isUserLoggedIn() === false) return 322 if (this.isUserLoggedIn() === false) return
321 323
322 this.videoService.getUserVideoRating(this.video.id) 324 this.videoService.getUserVideoRating(this.video.id)
323 .subscribe( 325 .subscribe(
324 ratingObject => { 326 ratingObject => {
325 if (ratingObject) { 327 if (ratingObject) {
326 this.userRating = ratingObject.rating 328 this.userRating = ratingObject.rating
327 } 329 }
328 }, 330 },
329 331
330 err => this.notificationsService.error(this.i18n('Error'), err.message) 332 err => this.notificationsService.error(this.i18n('Error'), err.message)
331 ) 333 )
332 } 334 }
333 335
334 private async onVideoFetched (video: VideoDetails, startTime = 0) { 336 private async onVideoFetched (video: VideoDetails, startTime = 0) {
@@ -409,14 +411,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
409 } 411 }
410 412
411 method.call(this.videoService, this.video.id) 413 method.call(this.videoService, this.video.id)
412 .subscribe( 414 .subscribe(
413 () => { 415 () => {
414 // Update the video like attribute 416 // Update the video like attribute
415 this.updateVideoRating(this.userRating, nextRating) 417 this.updateVideoRating(this.userRating, nextRating)
416 this.userRating = nextRating 418 this.userRating = nextRating
417 }, 419 },
418 err => this.notificationsService.error(this.i18n('Error'), err.message) 420
419 ) 421 err => this.notificationsService.error(this.i18n('Error'), err.message)
422 )
420 } 423 }
421 424
422 private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) { 425 private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) {