diff options
author | Chocobozzz <me@florianbigard.com> | 2020-09-17 09:20:52 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-11-09 15:33:04 +0100 |
commit | c6c0fa6cd8fe8f752463d8982c3dbcd448739c4e (patch) | |
tree | 79304b0152b0a38d33b26e65d4acdad0da4032a7 /client/src/app/+videos | |
parent | 110d463fece85e87a26aca48a6048ae0017a27b3 (diff) | |
download | PeerTube-c6c0fa6cd8fe8f752463d8982c3dbcd448739c4e.tar.gz PeerTube-c6c0fa6cd8fe8f752463d8982c3dbcd448739c4e.tar.zst PeerTube-c6c0fa6cd8fe8f752463d8982c3dbcd448739c4e.zip |
Live streaming implementation first step
Diffstat (limited to 'client/src/app/+videos')
15 files changed, 318 insertions, 100 deletions
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit-utils.ts b/client/src/app/+videos/+video-edit/shared/video-edit-utils.ts new file mode 100644 index 000000000..3a7dbed36 --- /dev/null +++ b/client/src/app/+videos/+video-edit/shared/video-edit-utils.ts | |||
@@ -0,0 +1,35 @@ | |||
1 | import { FormGroup } from '@angular/forms' | ||
2 | import { VideoEdit } from '@app/shared/shared-main' | ||
3 | |||
4 | function hydrateFormFromVideo (formGroup: FormGroup, video: VideoEdit, thumbnailFiles: boolean) { | ||
5 | formGroup.patchValue(video.toFormPatch()) | ||
6 | |||
7 | if (thumbnailFiles === false) return | ||
8 | |||
9 | const objects = [ | ||
10 | { | ||
11 | url: 'thumbnailUrl', | ||
12 | name: 'thumbnailfile' | ||
13 | }, | ||
14 | { | ||
15 | url: 'previewUrl', | ||
16 | name: 'previewfile' | ||
17 | } | ||
18 | ] | ||
19 | |||
20 | for (const obj of objects) { | ||
21 | if (!video[obj.url]) continue | ||
22 | |||
23 | fetch(video[obj.url]) | ||
24 | .then(response => response.blob()) | ||
25 | .then(data => { | ||
26 | formGroup.patchValue({ | ||
27 | [ obj.name ]: data | ||
28 | }) | ||
29 | }) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | export { | ||
34 | hydrateFormFromVideo | ||
35 | } | ||
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 842997b20..c444dd8d3 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 | |||
@@ -195,6 +195,29 @@ | |||
195 | </ng-template> | 195 | </ng-template> |
196 | </ng-container> | 196 | </ng-container> |
197 | 197 | ||
198 | <ng-container ngbNavItem *ngIf="videoLive"> | ||
199 | <a ngbNavLink i18n>Live settings</a> | ||
200 | |||
201 | <ng-template ngbNavContent> | ||
202 | <div class="row live-settings"> | ||
203 | <div class="col-md-12"> | ||
204 | |||
205 | <div class="form-group"> | ||
206 | <label for="videoLiveRTMPUrl" i18n>Live RTMP Url</label> | ||
207 | <my-input-readonly-copy id="videoLiveRTMPUrl" [value]="videoLive.rtmpUrl"></my-input-readonly-copy> | ||
208 | </div> | ||
209 | |||
210 | <div class="form-group"> | ||
211 | <label for="videoLiveStreamKey" i18n>Live stream key</label> | ||
212 | <my-input-readonly-copy id="videoLiveStreamKey" [value]="videoLive.streamKey"></my-input-readonly-copy> | ||
213 | </div> | ||
214 | </div> | ||
215 | </div> | ||
216 | </ng-template> | ||
217 | |||
218 | </ng-container> | ||
219 | |||
220 | |||
198 | <ng-container ngbNavItem> | 221 | <ng-container ngbNavItem> |
199 | <a ngbNavLink i18n>Advanced settings</a> | 222 | <a ngbNavLink i18n>Advanced settings</a> |
200 | 223 | ||
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 f04111e69..bee65184b 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 | |||
@@ -20,10 +20,11 @@ import { | |||
20 | import { FormReactiveValidationMessages, FormValidatorService, SelectChannelItem } from '@app/shared/shared-forms' | 20 | import { FormReactiveValidationMessages, FormValidatorService, SelectChannelItem } from '@app/shared/shared-forms' |
21 | import { InstanceService } from '@app/shared/shared-instance' | 21 | import { InstanceService } from '@app/shared/shared-instance' |
22 | import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main' | 22 | import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main' |
23 | import { ServerConfig, VideoConstant, VideoPrivacy } from '@shared/models' | 23 | import { ServerConfig, VideoConstant, VideoLive, VideoPrivacy } from '@shared/models' |
24 | import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model' | 24 | import { RegisterClientFormFieldOptions, RegisterClientVideoFieldOptions } from '@shared/models/plugins/register-client-form-field.model' |
25 | import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' | 25 | import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' |
26 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' | 26 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' |
27 | import { VideoEditType } from './video-edit.type' | ||
27 | 28 | ||
28 | type VideoLanguages = VideoConstant<string> & { group?: string } | 29 | type VideoLanguages = VideoConstant<string> & { group?: string } |
29 | 30 | ||
@@ -40,7 +41,8 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
40 | @Input() schedulePublicationPossible = true | 41 | @Input() schedulePublicationPossible = true |
41 | @Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = [] | 42 | @Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = [] |
42 | @Input() waitTranscodingEnabled = true | 43 | @Input() waitTranscodingEnabled = true |
43 | @Input() type: 'import-url' | 'import-torrent' | 'upload' | 'update' | 44 | @Input() type: VideoEditType |
45 | @Input() videoLive: VideoLive | ||
44 | 46 | ||
45 | @ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent | 47 | @ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent |
46 | 48 | ||
@@ -124,7 +126,8 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
124 | previewfile: null, | 126 | previewfile: null, |
125 | support: VIDEO_SUPPORT_VALIDATOR, | 127 | support: VIDEO_SUPPORT_VALIDATOR, |
126 | schedulePublicationAt: VIDEO_SCHEDULE_PUBLICATION_AT_VALIDATOR, | 128 | schedulePublicationAt: VIDEO_SCHEDULE_PUBLICATION_AT_VALIDATOR, |
127 | originallyPublishedAt: VIDEO_ORIGINALLY_PUBLISHED_AT_VALIDATOR | 129 | originallyPublishedAt: VIDEO_ORIGINALLY_PUBLISHED_AT_VALIDATOR, |
130 | liveStreamKey: null | ||
128 | } | 131 | } |
129 | 132 | ||
130 | this.formValidatorService.updateForm( | 133 | this.formValidatorService.updateForm( |
@@ -320,7 +323,12 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
320 | const currentSupport = this.form.value[ 'support' ] | 323 | const currentSupport = this.form.value[ 'support' ] |
321 | 324 | ||
322 | // First time we set the channel? | 325 | // First time we set the channel? |
323 | if (isNaN(oldChannelId) && !currentSupport) return this.updateSupportField(newChannel.support) | 326 | if (isNaN(oldChannelId)) { |
327 | // Fill support if it's empty | ||
328 | if (!currentSupport) this.updateSupportField(newChannel.support) | ||
329 | |||
330 | return | ||
331 | } | ||
324 | 332 | ||
325 | const oldChannel = this.userVideoChannels.find(c => c.id === oldChannelId) | 333 | const oldChannel = this.userVideoChannels.find(c => c.id === oldChannelId) |
326 | if (!newChannel || !oldChannel) { | 334 | if (!newChannel || !oldChannel) { |
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.type.ts b/client/src/app/+videos/+video-edit/shared/video-edit.type.ts new file mode 100644 index 000000000..fdbe9505c --- /dev/null +++ b/client/src/app/+videos/+video-edit/shared/video-edit.type.ts | |||
@@ -0,0 +1 @@ | |||
export type VideoEditType = 'update' | 'upload' | 'import-url' | 'import-torrent' | 'go-live' | |||
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.html b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.html new file mode 100644 index 000000000..6997f5388 --- /dev/null +++ b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.html | |||
@@ -0,0 +1,47 @@ | |||
1 | <div *ngIf="!isInUpdateForm" class="upload-video-container"> | ||
2 | <div class="first-step-block"> | ||
3 | <my-global-icon class="upload-icon" iconName="upload" aria-hidden="true"></my-global-icon> | ||
4 | |||
5 | <div class="form-group"> | ||
6 | <label i18n for="first-step-channel">Channel</label> | ||
7 | <my-select-channel | ||
8 | labelForId="first-step-channel" [items]="userVideoChannels" [(ngModel)]="firstStepChannelId" | ||
9 | ></my-select-channel> | ||
10 | </div> | ||
11 | |||
12 | <div class="form-group"> | ||
13 | <label i18n for="first-step-privacy">Privacy</label> | ||
14 | <my-select-options | ||
15 | labelForId="first-step-privacy" [items]="videoPrivacies" [(ngModel)]="firstStepPrivacyId" | ||
16 | ></my-select-options> | ||
17 | </div> | ||
18 | |||
19 | <input | ||
20 | type="button" i18n-value value="Go Live" (click)="goLive()" | ||
21 | /> | ||
22 | </div> | ||
23 | </div> | ||
24 | |||
25 | <div *ngIf="error" class="alert alert-danger"> | ||
26 | <div i18n>Sorry, but something went wrong</div> | ||
27 | {{ error }} | ||
28 | </div> | ||
29 | |||
30 | <!-- Hidden because we want to load the component --> | ||
31 | <form [hidden]="!isInUpdateForm" novalidate [formGroup]="form"> | ||
32 | <my-video-edit | ||
33 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" [schedulePublicationPossible]="false" | ||
34 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" [videoLive]="videoLive" | ||
35 | type="go-live" | ||
36 | ></my-video-edit> | ||
37 | |||
38 | <div class="submit-container"> | ||
39 | <div class="submit-button" | ||
40 | (click)="updateSecondStep()" | ||
41 | [ngClass]="{ disabled: !form.valid }" | ||
42 | > | ||
43 | <my-global-icon iconName="circle-tick" aria-hidden="true"></my-global-icon> | ||
44 | <input type="button" i18n-value value="Update" /> | ||
45 | </div> | ||
46 | </div> | ||
47 | </form> | ||
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts new file mode 100644 index 000000000..64fd4c4d4 --- /dev/null +++ b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts | |||
@@ -0,0 +1,129 @@ | |||
1 | |||
2 | import { Component, EventEmitter, OnInit, Output } from '@angular/core' | ||
3 | import { Router } from '@angular/router' | ||
4 | import { AuthService, CanComponentDeactivate, Notifier, ServerService } from '@app/core' | ||
5 | import { scrollToTop } from '@app/helpers' | ||
6 | import { FormValidatorService } from '@app/shared/shared-forms' | ||
7 | import { VideoCaptionService, VideoEdit, VideoService, VideoLiveService } from '@app/shared/shared-main' | ||
8 | import { LoadingBarService } from '@ngx-loading-bar/core' | ||
9 | import { VideoCreate, VideoLive, VideoPrivacy } from '@shared/models' | ||
10 | import { VideoSend } from './video-send' | ||
11 | |||
12 | @Component({ | ||
13 | selector: 'my-video-go-live', | ||
14 | templateUrl: './video-go-live.component.html', | ||
15 | styleUrls: [ | ||
16 | '../shared/video-edit.component.scss', | ||
17 | './video-send.scss' | ||
18 | ] | ||
19 | }) | ||
20 | export class VideoGoLiveComponent extends VideoSend implements OnInit, CanComponentDeactivate { | ||
21 | @Output() firstStepDone = new EventEmitter<string>() | ||
22 | @Output() firstStepError = new EventEmitter<void>() | ||
23 | |||
24 | isInUpdateForm = false | ||
25 | |||
26 | videoLive: VideoLive | ||
27 | videoId: number | ||
28 | videoUUID: string | ||
29 | error: string | ||
30 | |||
31 | protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC | ||
32 | |||
33 | constructor ( | ||
34 | protected formValidatorService: FormValidatorService, | ||
35 | protected loadingBar: LoadingBarService, | ||
36 | protected notifier: Notifier, | ||
37 | protected authService: AuthService, | ||
38 | protected serverService: ServerService, | ||
39 | protected videoService: VideoService, | ||
40 | protected videoCaptionService: VideoCaptionService, | ||
41 | private videoLiveService: VideoLiveService, | ||
42 | private router: Router | ||
43 | ) { | ||
44 | super() | ||
45 | } | ||
46 | |||
47 | ngOnInit () { | ||
48 | super.ngOnInit() | ||
49 | } | ||
50 | |||
51 | canDeactivate () { | ||
52 | return { canDeactivate: true } | ||
53 | } | ||
54 | |||
55 | goLive () { | ||
56 | const video: VideoCreate = { | ||
57 | name: 'Live', | ||
58 | privacy: VideoPrivacy.PRIVATE, | ||
59 | nsfw: this.serverConfig.instance.isNSFW, | ||
60 | waitTranscoding: true, | ||
61 | commentsEnabled: true, | ||
62 | downloadEnabled: true, | ||
63 | channelId: this.firstStepChannelId | ||
64 | } | ||
65 | |||
66 | this.firstStepDone.emit(name) | ||
67 | |||
68 | // Go live in private mode, but correctly fill the update form with the first user choice | ||
69 | const toPatch = Object.assign({}, video, { privacy: this.firstStepPrivacyId }) | ||
70 | this.form.patchValue(toPatch) | ||
71 | |||
72 | this.videoLiveService.goLive(video).subscribe( | ||
73 | res => { | ||
74 | this.videoId = res.video.id | ||
75 | this.videoUUID = res.video.uuid | ||
76 | this.isInUpdateForm = true | ||
77 | |||
78 | this.fetchVideoLive() | ||
79 | }, | ||
80 | |||
81 | err => { | ||
82 | this.firstStepError.emit() | ||
83 | this.notifier.error(err.message) | ||
84 | } | ||
85 | ) | ||
86 | } | ||
87 | |||
88 | updateSecondStep () { | ||
89 | if (this.checkForm() === false) { | ||
90 | return | ||
91 | } | ||
92 | |||
93 | const video = new VideoEdit() | ||
94 | video.patch(this.form.value) | ||
95 | video.id = this.videoId | ||
96 | video.uuid = this.videoUUID | ||
97 | |||
98 | // Update the video | ||
99 | this.updateVideoAndCaptions(video) | ||
100 | .subscribe( | ||
101 | () => { | ||
102 | this.notifier.success($localize`Live published.`) | ||
103 | |||
104 | this.router.navigate([ '/videos/watch', video.uuid ]) | ||
105 | }, | ||
106 | |||
107 | err => { | ||
108 | this.error = err.message | ||
109 | scrollToTop() | ||
110 | console.error(err) | ||
111 | } | ||
112 | ) | ||
113 | |||
114 | } | ||
115 | |||
116 | private fetchVideoLive () { | ||
117 | this.videoLiveService.getVideoLive(this.videoId) | ||
118 | .subscribe( | ||
119 | videoLive => { | ||
120 | this.videoLive = videoLive | ||
121 | }, | ||
122 | |||
123 | err => { | ||
124 | this.firstStepError.emit() | ||
125 | this.notifier.error(err.message) | ||
126 | } | ||
127 | ) | ||
128 | } | ||
129 | } | ||
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts index e9ad8af7a..64e887987 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts +++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts | |||
@@ -6,6 +6,7 @@ import { FormValidatorService } from '@app/shared/shared-forms' | |||
6 | import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' | 6 | import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' |
7 | import { LoadingBarService } from '@ngx-loading-bar/core' | 7 | import { LoadingBarService } from '@ngx-loading-bar/core' |
8 | import { VideoPrivacy, VideoUpdate } from '@shared/models' | 8 | import { VideoPrivacy, VideoUpdate } from '@shared/models' |
9 | import { hydrateFormFromVideo } from '../shared/video-edit-utils' | ||
9 | import { VideoSend } from './video-send' | 10 | import { VideoSend } from './video-send' |
10 | 11 | ||
11 | @Component({ | 12 | @Component({ |
@@ -99,7 +100,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca | |||
99 | previewUrl: null | 100 | previewUrl: null |
100 | })) | 101 | })) |
101 | 102 | ||
102 | this.hydrateFormFromVideo() | 103 | hydrateFormFromVideo(this.form, this.video, false) |
103 | }, | 104 | }, |
104 | 105 | ||
105 | err => { | 106 | err => { |
@@ -136,10 +137,5 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca | |||
136 | console.error(err) | 137 | console.error(err) |
137 | } | 138 | } |
138 | ) | 139 | ) |
139 | |||
140 | } | ||
141 | |||
142 | private hydrateFormFromVideo () { | ||
143 | this.form.patchValue(this.video.toFormPatch()) | ||
144 | } | 140 | } |
145 | } | 141 | } |
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts index 8bad81097..47f59a5d0 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts +++ b/client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts | |||
@@ -7,6 +7,7 @@ import { FormValidatorService } from '@app/shared/shared-forms' | |||
7 | import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' | 7 | import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' |
8 | import { LoadingBarService } from '@ngx-loading-bar/core' | 8 | import { LoadingBarService } from '@ngx-loading-bar/core' |
9 | import { VideoPrivacy, VideoUpdate } from '@shared/models' | 9 | import { VideoPrivacy, VideoUpdate } from '@shared/models' |
10 | import { hydrateFormFromVideo } from '../shared/video-edit-utils' | ||
10 | import { VideoSend } from './video-send' | 11 | import { VideoSend } from './video-send' |
11 | 12 | ||
12 | @Component({ | 13 | @Component({ |
@@ -109,7 +110,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom | |||
109 | 110 | ||
110 | this.videoCaptions = videoCaptions | 111 | this.videoCaptions = videoCaptions |
111 | 112 | ||
112 | this.hydrateFormFromVideo() | 113 | hydrateFormFromVideo(this.form, this.video, true) |
113 | }, | 114 | }, |
114 | 115 | ||
115 | err => { | 116 | err => { |
@@ -146,31 +147,5 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom | |||
146 | console.error(err) | 147 | console.error(err) |
147 | } | 148 | } |
148 | ) | 149 | ) |
149 | |||
150 | } | ||
151 | |||
152 | private hydrateFormFromVideo () { | ||
153 | this.form.patchValue(this.video.toFormPatch()) | ||
154 | |||
155 | const objects = [ | ||
156 | { | ||
157 | url: 'thumbnailUrl', | ||
158 | name: 'thumbnailfile' | ||
159 | }, | ||
160 | { | ||
161 | url: 'previewUrl', | ||
162 | name: 'previewfile' | ||
163 | } | ||
164 | ] | ||
165 | |||
166 | for (const obj of objects) { | ||
167 | fetch(this.video[obj.url]) | ||
168 | .then(response => response.blob()) | ||
169 | .then(data => { | ||
170 | this.form.patchValue({ | ||
171 | [ obj.name ]: data | ||
172 | }) | ||
173 | }) | ||
174 | } | ||
175 | } | 150 | } |
176 | } | 151 | } |
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts index 32a17052a..258f5c7a0 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts +++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts | |||
@@ -157,7 +157,6 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
157 | this.waitTranscodingEnabled = false | 157 | this.waitTranscodingEnabled = false |
158 | } | 158 | } |
159 | 159 | ||
160 | const privacy = this.firstStepPrivacyId.toString() | ||
161 | const nsfw = this.serverConfig.instance.isNSFW | 160 | const nsfw = this.serverConfig.instance.isNSFW |
162 | const waitTranscoding = true | 161 | const waitTranscoding = true |
163 | const commentsEnabled = true | 162 | const commentsEnabled = true |
diff --git a/client/src/app/+videos/+video-edit/video-add.component.html b/client/src/app/+videos/+video-edit/video-add.component.html index 14d41f95b..bf2cc9c83 100644 --- a/client/src/app/+videos/+video-edit/video-add.component.html +++ b/client/src/app/+videos/+video-edit/video-add.component.html | |||
@@ -50,7 +50,17 @@ | |||
50 | <my-video-import-torrent #videoImportTorrent (firstStepDone)="onFirstStepDone('import-torrent', $event)" (firstStepError)="onError()"></my-video-import-torrent> | 50 | <my-video-import-torrent #videoImportTorrent (firstStepDone)="onFirstStepDone('import-torrent', $event)" (firstStepError)="onError()"></my-video-import-torrent> |
51 | </ng-template> | 51 | </ng-template> |
52 | </ng-container> | 52 | </ng-container> |
53 | |||
54 | <ng-container ngbNavItem *ngIf="isVideoLiveEnabled()"> | ||
55 | <a ngbNavLink> | ||
56 | <span i18n>Go live</span> | ||
57 | </a> | ||
58 | |||
59 | <ng-template ngbNavContent> | ||
60 | <my-video-go-live #videoGoLive (firstStepDone)="onFirstStepDone('go-live', $event)" (firstStepError)="onError()"></my-video-go-live> | ||
61 | </ng-template> | ||
62 | </ng-container> | ||
53 | </div> | 63 | </div> |
54 | 64 | ||
55 | <div [ngbNavOutlet]="nav"></div> | 65 | <div [ngbNavOutlet]="nav"></div> |
56 | </div> \ No newline at end of file | 66 | </div> |
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 94e85efc1..441d5a3db 100644 --- a/client/src/app/+videos/+video-edit/video-add.component.ts +++ b/client/src/app/+videos/+video-edit/video-add.component.ts | |||
@@ -1,6 +1,8 @@ | |||
1 | import { Component, HostListener, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, HostListener, OnInit, ViewChild } from '@angular/core' |
2 | import { AuthService, AuthUser, CanComponentDeactivate, ServerService } from '@app/core' | 2 | import { AuthService, AuthUser, CanComponentDeactivate, ServerService } from '@app/core' |
3 | import { ServerConfig } from '@shared/models' | 3 | import { ServerConfig } from '@shared/models' |
4 | import { VideoEditType } from './shared/video-edit.type' | ||
5 | import { VideoGoLiveComponent } from './video-add-components/video-go-live.component' | ||
4 | import { VideoImportTorrentComponent } from './video-add-components/video-import-torrent.component' | 6 | import { VideoImportTorrentComponent } from './video-add-components/video-import-torrent.component' |
5 | import { VideoImportUrlComponent } from './video-add-components/video-import-url.component' | 7 | import { VideoImportUrlComponent } from './video-add-components/video-import-url.component' |
6 | import { VideoUploadComponent } from './video-add-components/video-upload.component' | 8 | import { VideoUploadComponent } from './video-add-components/video-upload.component' |
@@ -14,10 +16,11 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate { | |||
14 | @ViewChild('videoUpload') videoUpload: VideoUploadComponent | 16 | @ViewChild('videoUpload') videoUpload: VideoUploadComponent |
15 | @ViewChild('videoImportUrl') videoImportUrl: VideoImportUrlComponent | 17 | @ViewChild('videoImportUrl') videoImportUrl: VideoImportUrlComponent |
16 | @ViewChild('videoImportTorrent') videoImportTorrent: VideoImportTorrentComponent | 18 | @ViewChild('videoImportTorrent') videoImportTorrent: VideoImportTorrentComponent |
19 | @ViewChild('videoGoLive') videoGoLive: VideoGoLiveComponent | ||
17 | 20 | ||
18 | user: AuthUser = null | 21 | user: AuthUser = null |
19 | 22 | ||
20 | secondStepType: 'upload' | 'import-url' | 'import-torrent' | 23 | secondStepType: VideoEditType |
21 | videoName: string | 24 | videoName: string |
22 | serverConfig: ServerConfig | 25 | serverConfig: ServerConfig |
23 | 26 | ||
@@ -41,7 +44,7 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate { | |||
41 | this.user = this.auth.getUser() | 44 | this.user = this.auth.getUser() |
42 | } | 45 | } |
43 | 46 | ||
44 | onFirstStepDone (type: 'upload' | 'import-url' | 'import-torrent', videoName: string) { | 47 | onFirstStepDone (type: VideoEditType, videoName: string) { |
45 | this.secondStepType = type | 48 | this.secondStepType = type |
46 | this.videoName = videoName | 49 | this.videoName = videoName |
47 | } | 50 | } |
@@ -62,9 +65,9 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate { | |||
62 | } | 65 | } |
63 | 66 | ||
64 | canDeactivate (): { canDeactivate: boolean, text?: string} { | 67 | canDeactivate (): { canDeactivate: boolean, text?: string} { |
65 | if (this.secondStepType === 'upload') return this.videoUpload.canDeactivate() | ||
66 | if (this.secondStepType === 'import-url') return this.videoImportUrl.canDeactivate() | 68 | if (this.secondStepType === 'import-url') return this.videoImportUrl.canDeactivate() |
67 | if (this.secondStepType === 'import-torrent') return this.videoImportTorrent.canDeactivate() | 69 | if (this.secondStepType === 'import-torrent') return this.videoImportTorrent.canDeactivate() |
70 | if (this.secondStepType === 'go-live') return this.videoGoLive.canDeactivate() | ||
68 | 71 | ||
69 | return { canDeactivate: true } | 72 | return { canDeactivate: true } |
70 | } | 73 | } |
@@ -77,6 +80,10 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate { | |||
77 | return this.serverConfig.import.videos.torrent.enabled | 80 | return this.serverConfig.import.videos.torrent.enabled |
78 | } | 81 | } |
79 | 82 | ||
83 | isVideoLiveEnabled () { | ||
84 | return this.serverConfig.live.enabled | ||
85 | } | ||
86 | |||
80 | isInSecondStep () { | 87 | isInSecondStep () { |
81 | return !!this.secondStepType | 88 | return !!this.secondStepType |
82 | } | 89 | } |
diff --git a/client/src/app/+videos/+video-edit/video-add.module.ts b/client/src/app/+videos/+video-edit/video-add.module.ts index 477c1cf5e..da651119b 100644 --- a/client/src/app/+videos/+video-edit/video-add.module.ts +++ b/client/src/app/+videos/+video-edit/video-add.module.ts | |||
@@ -4,6 +4,7 @@ import { VideoEditModule } from './shared/video-edit.module' | |||
4 | import { DragDropDirective } from './video-add-components/drag-drop.directive' | 4 | import { DragDropDirective } from './video-add-components/drag-drop.directive' |
5 | import { VideoImportTorrentComponent } from './video-add-components/video-import-torrent.component' | 5 | import { VideoImportTorrentComponent } from './video-add-components/video-import-torrent.component' |
6 | import { VideoImportUrlComponent } from './video-add-components/video-import-url.component' | 6 | import { VideoImportUrlComponent } from './video-add-components/video-import-url.component' |
7 | import { VideoGoLiveComponent } from './video-add-components/video-go-live.component' | ||
7 | import { VideoUploadComponent } from './video-add-components/video-upload.component' | 8 | import { VideoUploadComponent } from './video-add-components/video-upload.component' |
8 | import { VideoAddRoutingModule } from './video-add-routing.module' | 9 | import { VideoAddRoutingModule } from './video-add-routing.module' |
9 | import { VideoAddComponent } from './video-add.component' | 10 | import { VideoAddComponent } from './video-add.component' |
@@ -20,7 +21,8 @@ import { VideoAddComponent } from './video-add.component' | |||
20 | VideoUploadComponent, | 21 | VideoUploadComponent, |
21 | VideoImportUrlComponent, | 22 | VideoImportUrlComponent, |
22 | VideoImportTorrentComponent, | 23 | VideoImportTorrentComponent, |
23 | DragDropDirective | 24 | DragDropDirective, |
25 | VideoGoLiveComponent | ||
24 | ], | 26 | ], |
25 | 27 | ||
26 | exports: [ ], | 28 | exports: [ ], |
diff --git a/client/src/app/+videos/+video-edit/video-update.component.html b/client/src/app/+videos/+video-edit/video-update.component.html index b37596399..5f50ddc74 100644 --- a/client/src/app/+videos/+video-edit/video-update.component.html +++ b/client/src/app/+videos/+video-edit/video-update.component.html | |||
@@ -11,6 +11,7 @@ | |||
11 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" | 11 | [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels" |
12 | [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="waitTranscodingEnabled" | 12 | [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="waitTranscodingEnabled" |
13 | type="update" (pluginFieldsAdded)="hydratePluginFieldsFromVideo()" | 13 | type="update" (pluginFieldsAdded)="hydratePluginFieldsFromVideo()" |
14 | [videoLive]="videoLive" | ||
14 | ></my-video-edit> | 15 | ></my-video-edit> |
15 | 16 | ||
16 | <div class="submit-container"> | 17 | <div class="submit-container"> |
diff --git a/client/src/app/+videos/+video-edit/video-update.component.ts b/client/src/app/+videos/+video-edit/video-update.component.ts index 20438a2d3..c0f46acd2 100644 --- a/client/src/app/+videos/+video-edit/video-update.component.ts +++ b/client/src/app/+videos/+video-edit/video-update.component.ts | |||
@@ -5,7 +5,8 @@ import { Notifier } from '@app/core' | |||
5 | import { FormReactive, FormValidatorService, SelectChannelItem } from '@app/shared/shared-forms' | 5 | import { FormReactive, FormValidatorService, SelectChannelItem } from '@app/shared/shared-forms' |
6 | import { VideoCaptionEdit, VideoCaptionService, VideoDetails, VideoEdit, VideoService } from '@app/shared/shared-main' | 6 | import { VideoCaptionEdit, VideoCaptionService, VideoDetails, VideoEdit, VideoService } from '@app/shared/shared-main' |
7 | import { LoadingBarService } from '@ngx-loading-bar/core' | 7 | import { LoadingBarService } from '@ngx-loading-bar/core' |
8 | import { VideoPrivacy } from '@shared/models' | 8 | import { VideoPrivacy, VideoLive } from '@shared/models' |
9 | import { hydrateFormFromVideo } from './shared/video-edit-utils' | ||
9 | 10 | ||
10 | @Component({ | 11 | @Component({ |
11 | selector: 'my-videos-update', | 12 | selector: 'my-videos-update', |
@@ -14,11 +15,12 @@ import { VideoPrivacy } from '@shared/models' | |||
14 | }) | 15 | }) |
15 | export class VideoUpdateComponent extends FormReactive implements OnInit { | 16 | export class VideoUpdateComponent extends FormReactive implements OnInit { |
16 | video: VideoEdit | 17 | video: VideoEdit |
18 | userVideoChannels: SelectChannelItem[] = [] | ||
19 | videoCaptions: VideoCaptionEdit[] = [] | ||
20 | videoLive: VideoLive | ||
17 | 21 | ||
18 | isUpdatingVideo = false | 22 | isUpdatingVideo = false |
19 | userVideoChannels: SelectChannelItem[] = [] | ||
20 | schedulePublicationPossible = false | 23 | schedulePublicationPossible = false |
21 | videoCaptions: VideoCaptionEdit[] = [] | ||
22 | waitTranscodingEnabled = true | 24 | waitTranscodingEnabled = true |
23 | 25 | ||
24 | private updateDone = false | 26 | private updateDone = false |
@@ -40,10 +42,11 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
40 | 42 | ||
41 | this.route.data | 43 | this.route.data |
42 | .pipe(map(data => data.videoData)) | 44 | .pipe(map(data => data.videoData)) |
43 | .subscribe(({ video, videoChannels, videoCaptions }) => { | 45 | .subscribe(({ video, videoChannels, videoCaptions, videoLive }) => { |
44 | this.video = new VideoEdit(video) | 46 | this.video = new VideoEdit(video) |
45 | this.userVideoChannels = videoChannels | 47 | this.userVideoChannels = videoChannels |
46 | this.videoCaptions = videoCaptions | 48 | this.videoCaptions = videoCaptions |
49 | this.videoLive = videoLive | ||
47 | 50 | ||
48 | this.schedulePublicationPossible = this.video.privacy === VideoPrivacy.PRIVATE | 51 | this.schedulePublicationPossible = this.video.privacy === VideoPrivacy.PRIVATE |
49 | 52 | ||
@@ -53,7 +56,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
53 | } | 56 | } |
54 | 57 | ||
55 | // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout | 58 | // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout |
56 | setTimeout(() => this.hydrateFormFromVideo()) | 59 | setTimeout(() => hydrateFormFromVideo(this.form, this.video, true)) |
57 | }, | 60 | }, |
58 | 61 | ||
59 | err => { | 62 | err => { |
@@ -133,29 +136,4 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
133 | pluginData: this.video.pluginData | 136 | pluginData: this.video.pluginData |
134 | }) | 137 | }) |
135 | } | 138 | } |
136 | |||
137 | private hydrateFormFromVideo () { | ||
138 | this.form.patchValue(this.video.toFormPatch()) | ||
139 | |||
140 | const objects = [ | ||
141 | { | ||
142 | url: 'thumbnailUrl', | ||
143 | name: 'thumbnailfile' | ||
144 | }, | ||
145 | { | ||
146 | url: 'previewUrl', | ||
147 | name: 'previewfile' | ||
148 | } | ||
149 | ] | ||
150 | |||
151 | for (const obj of objects) { | ||
152 | fetch(this.video[obj.url]) | ||
153 | .then(response => response.blob()) | ||
154 | .then(data => { | ||
155 | this.form.patchValue({ | ||
156 | [ obj.name ]: data | ||
157 | }) | ||
158 | }) | ||
159 | } | ||
160 | } | ||
161 | } | 139 | } |
diff --git a/client/src/app/+videos/+video-edit/video-update.resolver.ts b/client/src/app/+videos/+video-edit/video-update.resolver.ts index a391913d8..3a82324c3 100644 --- a/client/src/app/+videos/+video-edit/video-update.resolver.ts +++ b/client/src/app/+videos/+video-edit/video-update.resolver.ts | |||
@@ -1,13 +1,14 @@ | |||
1 | import { forkJoin } from 'rxjs' | 1 | import { forkJoin, of } from 'rxjs' |
2 | import { map, switchMap } from 'rxjs/operators' | 2 | import { map, switchMap } from 'rxjs/operators' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { ActivatedRouteSnapshot, Resolve } from '@angular/router' | 4 | import { ActivatedRouteSnapshot, Resolve } from '@angular/router' |
5 | import { VideoCaptionService, VideoChannelService, VideoService } from '@app/shared/shared-main' | 5 | import { VideoCaptionService, VideoChannelService, VideoDetails, VideoLiveService, VideoService } from '@app/shared/shared-main' |
6 | 6 | ||
7 | @Injectable() | 7 | @Injectable() |
8 | export class VideoUpdateResolver implements Resolve<any> { | 8 | export class VideoUpdateResolver implements Resolve<any> { |
9 | constructor ( | 9 | constructor ( |
10 | private videoService: VideoService, | 10 | private videoService: VideoService, |
11 | private videoLiveService: VideoLiveService, | ||
11 | private videoChannelService: VideoChannelService, | 12 | private videoChannelService: VideoChannelService, |
12 | private videoCaptionService: VideoCaptionService | 13 | private videoCaptionService: VideoCaptionService |
13 | ) { | 14 | ) { |
@@ -18,32 +19,38 @@ export class VideoUpdateResolver implements Resolve<any> { | |||
18 | 19 | ||
19 | return this.videoService.getVideo({ videoId: uuid }) | 20 | return this.videoService.getVideo({ videoId: uuid }) |
20 | .pipe( | 21 | .pipe( |
21 | switchMap(video => { | 22 | switchMap(video => forkJoin(this.buildVideoObservables(video))), |
22 | return forkJoin([ | 23 | map(([ video, videoChannels, videoCaptions, videoLive ]) => ({ video, videoChannels, videoCaptions, videoLive })) |
23 | this.videoService | 24 | ) |
24 | .loadCompleteDescription(video.descriptionPath) | 25 | } |
25 | .pipe(map(description => Object.assign(video, { description }))), | ||
26 | 26 | ||
27 | this.videoChannelService | 27 | private buildVideoObservables (video: VideoDetails) { |
28 | .listAccountVideoChannels(video.account) | 28 | return [ |
29 | .pipe( | 29 | this.videoService |
30 | map(result => result.data), | 30 | .loadCompleteDescription(video.descriptionPath) |
31 | map(videoChannels => videoChannels.map(c => ({ | 31 | .pipe(map(description => Object.assign(video, { description }))), |
32 | id: c.id, | ||
33 | label: c.displayName, | ||
34 | support: c.support, | ||
35 | avatarPath: c.avatar?.path | ||
36 | }))) | ||
37 | ), | ||
38 | 32 | ||
39 | this.videoCaptionService | 33 | this.videoChannelService |
40 | .listCaptions(video.id) | 34 | .listAccountVideoChannels(video.account) |
41 | .pipe( | 35 | .pipe( |
42 | map(result => result.data) | 36 | map(result => result.data), |
43 | ) | 37 | map(videoChannels => videoChannels.map(c => ({ |
44 | ]) | 38 | id: c.id, |
45 | }), | 39 | label: c.displayName, |
46 | map(([ video, videoChannels, videoCaptions ]) => ({ video, videoChannels, videoCaptions })) | 40 | support: c.support, |
47 | ) | 41 | avatarPath: c.avatar?.path |
42 | }))) | ||
43 | ), | ||
44 | |||
45 | this.videoCaptionService | ||
46 | .listCaptions(video.id) | ||
47 | .pipe( | ||
48 | map(result => result.data) | ||
49 | ), | ||
50 | |||
51 | video.isLive | ||
52 | ? this.videoLiveService.getVideoLive(video.id) | ||
53 | : of(undefined) | ||
54 | ] | ||
48 | } | 55 | } |
49 | } | 56 | } |