Skip to content

Commit be2aa5d

Browse files
committed
feat(entry/live): allow editing manual live streams KMCNG-2476
1 parent 1265ad8 commit be2aa5d

File tree

4 files changed

+173
-19
lines changed

4 files changed

+173
-19
lines changed

Diff for: src/applications/content-entries-app/entry/entry-live/entry-live-widget.service.ts

+102-9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {Injectable, OnDestroy} from '@angular/core';
2-
import {BehaviorSubject} from 'rxjs';
2+
import {asyncScheduler, BehaviorSubject, merge} from 'rxjs';
33
import { Observable, of as ObservableOf } from 'rxjs';
44
import { throwError } from 'rxjs';
5-
import { map, catchError } from 'rxjs/operators';
5+
import {map, catchError, observeOn} from 'rxjs/operators';
66
import {
7-
KalturaClient,
8-
KalturaMultiRequest,
7+
KalturaClient, KalturaLiveStreamConfiguration,
8+
KalturaMultiRequest, KalturaPlaybackProtocol,
99
KalturaSipSourceType,
1010
PexipGenerateSipUrlAction
1111
} from 'kaltura-ngx-client';
@@ -26,13 +26,14 @@ import {KalturaConversionProfileFilter} from 'kaltura-ngx-client';
2626
import {KalturaFilterPager} from 'kaltura-ngx-client';
2727
import {KalturaConversionProfileType} from 'kaltura-ngx-client';
2828
import {KalturaNullableBoolean} from 'kaltura-ngx-client';
29-
import {AreaBlockerMessage} from '@kaltura-ng/kaltura-ui';
29+
import {AreaBlockerMessage, KalturaValidators} from '@kaltura-ng/kaltura-ui';
3030
import {BaseEntryGetAction} from 'kaltura-ngx-client';
3131
import { KMCPermissions, KMCPermissionsService } from 'app-shared/kmc-shared/kmc-permissions';
3232
import { ContentEntryViewSections } from 'app-shared/kmc-shared/kmc-views/details-views/content-entry-view.service';
3333
import { LiveDashboardAppViewService } from 'app-shared/kmc-shared/kmc-views/component-views';
3434
import {KalturaLogger} from '@kaltura-ng/kaltura-logger';
3535
import { cancelOnDestroy, tag } from '@kaltura-ng/kaltura-common';
36+
import {FormBuilder, FormGroup} from "@angular/forms";
3637

3738
export interface bitrate {
3839
enabled: boolean,
@@ -65,6 +66,7 @@ export class EntryLiveWidget extends EntryWidget implements OnDestroy {
6566
public _manualStreamsConfiguration = [];
6667
public _bitrates: bitrate[] = [];
6768
public _availableBitrates = AVAIL_BITRATES;
69+
public _form: FormGroup;
6870

6971
public _autoStartOptions = [
7072
{label: this._appLocalization.get('applications.content.entryDetails.live.disabled'), value: true},
@@ -77,10 +79,45 @@ export class EntryLiveWidget extends EntryWidget implements OnDestroy {
7779
private _permissionsService: KMCPermissionsService,
7880
private _browserService: BrowserService,
7981
private _liveDasboardAppViewService: LiveDashboardAppViewService,
82+
private _fb: FormBuilder,
8083
logger: KalturaLogger) {
8184
super(ContentEntryViewSections.Live, logger);
8285
}
8386

87+
public initManualForm() {
88+
this._form = this._fb.group({
89+
flashHDSURL: ['', KalturaValidators.url],
90+
hlsStreamUrl: ['', KalturaValidators.url],
91+
dashStreamUrl: ['', KalturaValidators.url]
92+
},
93+
{
94+
validator: (formGroup: FormGroup) => {
95+
return this._atLeastOneUrlValidator(formGroup);
96+
}
97+
});
98+
99+
let flashHDSURL = this._manualStreamsConfiguration.find(stream => stream.label.split(" ")[0] === KalturaPlaybackProtocol.akamaiHds)?.url || '';
100+
if (flashHDSURL === '') {
101+
flashHDSURL = this._manualStreamsConfiguration.find(stream => stream.label.split(" ")[0] === KalturaPlaybackProtocol.hds)?.url || '';
102+
}
103+
const hlsStreamUrl = this._manualStreamsConfiguration.find(stream => stream.label.split(" ")[0] === KalturaPlaybackProtocol.appleHttp)?.url || '';
104+
const dashStreamUrl = this._manualStreamsConfiguration.find(stream => stream.label.split(" ")[0] === KalturaPlaybackProtocol.mpegDash)?.url || '';
105+
this._form.reset({ flashHDSURL, hlsStreamUrl, dashStreamUrl });
106+
107+
merge(this._form.valueChanges,
108+
this._form.statusChanges)
109+
.pipe(observeOn(asyncScheduler)) // using async scheduler so the form group status/dirty mode will be synchornized
110+
.pipe(cancelOnDestroy(this))
111+
.subscribe(
112+
() => {
113+
super.updateState({
114+
isValid: this._form.status !== 'INVALID',
115+
isDirty: this._form.dirty
116+
});
117+
}
118+
);
119+
}
120+
84121
protected onReset() {
85122
this._DVRStatus = "";
86123
this._manualLiveUrl = "";
@@ -118,8 +155,41 @@ export class EntryLiveWidget extends EntryWidget implements OnDestroy {
118155
(data as KalturaLiveStreamEntry).conversionProfileId = this._selectedConversionProfile;
119156
}
120157
if (this._liveType === "manual") {
121-
const entry = this.data as KalturaLiveStreamEntry;
122-
(data as KalturaLiveStreamEntry).hlsStreamUrl = this._manualLiveUrl;
158+
const entry = data as KalturaLiveStreamEntry;
159+
entry.liveStreamConfigurations = [];
160+
// save hls stream to main entry and stream configuration
161+
const hlsStreamUrl = this._form.controls['hlsStreamUrl'].value;
162+
entry.hlsStreamUrl = hlsStreamUrl;
163+
if (hlsStreamUrl && hlsStreamUrl.length) {
164+
const cfg = new KalturaLiveStreamConfiguration();
165+
cfg.protocol = KalturaPlaybackProtocol.appleHttp;
166+
cfg.url = hlsStreamUrl;
167+
entry.liveStreamConfigurations.push(cfg);
168+
}
169+
170+
// save flash stream to stream configuration
171+
const flashStreamUrl = this._form.controls['flashHDSURL'].value;
172+
if (flashStreamUrl && flashStreamUrl.length) {
173+
let protocol = KalturaPlaybackProtocol.akamaiHds;
174+
let flashStreamConfig = (this.data as KalturaLiveStreamEntry).liveStreamConfigurations.find(cfg => cfg.protocol === KalturaPlaybackProtocol.akamaiHds);
175+
if (!flashStreamConfig) {
176+
protocol = KalturaPlaybackProtocol.hds;
177+
}
178+
const cfg = new KalturaLiveStreamConfiguration();
179+
cfg.protocol = protocol;
180+
cfg.url = flashStreamUrl;
181+
entry.liveStreamConfigurations.push(cfg);
182+
}
183+
184+
// save dash stream to stream configuration
185+
const dashStreamUrl = this._form.controls['dashStreamUrl'].value;
186+
if (dashStreamUrl && dashStreamUrl.length) {
187+
const dashStreamConfig = (this.data as KalturaLiveStreamEntry).liveStreamConfigurations.find(cfg => cfg.protocol === KalturaPlaybackProtocol.mpegDash);
188+
const cfg = new KalturaLiveStreamConfiguration();
189+
cfg.protocol = KalturaPlaybackProtocol.mpegDash;
190+
cfg.url = dashStreamUrl;
191+
entry.liveStreamConfigurations.push(cfg);
192+
}
123193
}
124194
}
125195

@@ -152,12 +222,34 @@ export class EntryLiveWidget extends EntryWidget implements OnDestroy {
152222

153223
protected onValidate(wasActivated: boolean): Observable<{ isValid: boolean}> {
154224
return Observable.create(observer => {
155-
const isValid = this._liveType === "universal" ? this._validateBitrates({updateDirtyMode: false}) : true;
156-
observer.next({isValid});
225+
let isValid = this._liveType === "universal" ? this._validateBitrates({updateDirtyMode: false}) : true;
226+
if (this._liveType === "manual") {
227+
for (const controlName in this._form.controls) {
228+
if (this._form.controls.hasOwnProperty(controlName)) {
229+
if (this._form.get(controlName).errors !== null) {
230+
isValid = false;
231+
}
232+
}
233+
}
234+
if (this._form.errors !== null) {
235+
isValid = false;
236+
}
237+
}
238+
observer.next({isValid});
157239
observer.complete()
158240
});
159241
}
160242

243+
private _atLeastOneUrlValidator(formgroup: FormGroup) {
244+
if (!formgroup.controls['flashHDSURL'].value &&
245+
!formgroup.controls['hlsStreamUrl'].value &&
246+
!formgroup.controls['dashStreamUrl'].value) {
247+
return {atLeastOneUrl: true};
248+
} else {
249+
return null;
250+
}
251+
}
252+
161253
protected onActivate(firstTimeActivating : boolean) {
162254
// set live type and load data accordingly
163255
switch (this.data.sourceType.toString()) {
@@ -228,6 +320,7 @@ export class EntryLiveWidget extends EntryWidget implements OnDestroy {
228320
case KalturaSourceType.manualLiveStream.toString():
229321
this._liveType = "manual";
230322
this._setManualStreams();
323+
this.initManualForm();
231324
break;
232325
}
233326

Diff for: src/applications/content-entries-app/entry/entry-live/entry-live.component.html

+60-9
Original file line numberDiff line numberDiff line change
@@ -303,15 +303,66 @@
303303
</div>
304304

305305
<div *ngIf="_widgetService._liveType === 'manual'">
306-
<div class="kRow">
307-
<span class="kWideLabels">{{'applications.content.entryDetails.live.hlsStream' | translate}}</span>
308-
<input [(ngModel)]="_widgetService._manualLiveUrl" (ngModelChange)="_widgetService.setDirty()" pInputText class="urlInput"/>
309-
</div>
310-
<div class="kRow" *ngFor="let config of _widgetService._manualStreamsConfiguration">
311-
<!-- loop through liveStreamConfigurations array -->
312-
<span class="kWideLabels">{{config.label}}</span>
313-
<span>{{config.url}}</span>
314-
</div>
306+
<form [formGroup]="_widgetService._form" novalidate>
307+
<section class="kFormContainer">
308+
<div class="kFormSection">
309+
<div class="kRow">
310+
<span class="kWideLabels">{{'applications.content.entryDetails.live.hlsStream' | translate}}</span>
311+
<div class="inputWrapper">
312+
<input class="kControl" formControlName="hlsStreamUrl" pInputText [ngClass]="{'kHasError':!_widgetService._form.controls['hlsStreamUrl'].valid && _widgetService._form.controls['hlsStreamUrl'].touched}">
313+
<div *ngIf="_widgetService._form.controls['hlsStreamUrl'].touched">
314+
<div *ngIf="_widgetService._form.controls['hlsStreamUrl'].hasError('url')" class="error">
315+
{{'applications.upload.prepareLive.manualStreamType.errors.invalidUrl' | translate}}
316+
</div>
317+
</div>
318+
</div>
319+
</div>
320+
</div>
321+
322+
<div class="kFormSection">
323+
<div class="kRow">
324+
<span class="kWideLabels">{{'applications.upload.prepareLive.manualStreamType.flashHDSURL' | translate}}</span>
325+
<div class="inputWrapper">
326+
<input class="kControl" formControlName="flashHDSURL" pInputText [ngClass]="{'kHasError':!_widgetService._form.controls['flashHDSURL'].valid && _widgetService._form.controls['flashHDSURL'].touched}">
327+
<div *ngIf="_widgetService._form.controls['flashHDSURL'].touched">
328+
<div *ngIf="_widgetService._form.controls['flashHDSURL'].hasError('url')" class="error">
329+
{{'applications.upload.prepareLive.manualStreamType.errors.invalidUrl' | translate}}
330+
</div>
331+
</div>
332+
</div>
333+
</div>
334+
</div>
335+
336+
<div class="kFormSection">
337+
<div class="kRow">
338+
<span class="kWideLabels">{{'applications.upload.prepareLive.manualStreamType.dashStreamUrl' | translate}}</span>
339+
<div class="inputWrapper">
340+
<input class="kControl" formControlName="dashStreamUrl" pInputText [ngClass]="{'kHasError':!_widgetService._form.controls['dashStreamUrl'].valid && _widgetService._form.controls['dashStreamUrl'].touched}">
341+
<div *ngIf="_widgetService._form.controls['dashStreamUrl'].touched">
342+
<div *ngIf="_widgetService._form.controls['dashStreamUrl'].hasError('url')" class="error">
343+
{{'applications.upload.prepareLive.manualStreamType.errors.invalidUrl' | translate}}
344+
</div>
345+
</div>
346+
</div>
347+
</div>
348+
</div>
349+
</section>
350+
</form>
351+
352+
<div class="kRow">
353+
<div
354+
*ngIf="_widgetService._form.touched && _widgetService._form.hasError('atLeastOneUrl')"
355+
class="error">
356+
{{'applications.upload.prepareLive.manualStreamType.errors.enterUrl' | translate}}
357+
</div>
358+
</div>
359+
360+
<div class="kRow" *ngFor="let config of _widgetService._manualStreamsConfiguration">
361+
<!-- loop through unsupported liveStreamConfigurations array -->
362+
<span *ngIf="!config.label.startsWith('applehttp') && !config.label.startsWith('hds') && !config.label.startsWith('mpegdash') && !config.label.startsWith('hdnetworkmanifest')" class="kWideLabels">{{config.label}}</span>
363+
<span *ngIf="!config.label.startsWith('applehttp') && !config.label.startsWith('hds') && !config.label.startsWith('mpegdash') && !config.label.startsWith('hdnetworkmanifest')">{{config.url}}</span>
364+
</div>
365+
315366
</div>
316367
<span #anchor></span>
317368
</div>

Diff for: src/applications/content-entries-app/entry/entry-live/entry-live.component.scss

+10
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@
6565
vertical-align: middle;
6666
}
6767
}
68+
}
69+
input.kControl, .inputWrapper {
70+
width: 100%;
71+
max-width: 500px;
6872
}
6973
}
7074
.kCopyBtn{
@@ -95,6 +99,12 @@
9599
text-overflow: ellipsis;
96100
overflow: hidden;
97101
}
102+
.kHasError {
103+
border-color: $kDandger;
104+
}
105+
.error {
106+
margin-top: 4px;
107+
}
98108
.srtKeyInput {
99109
width: 260px;
100110
height: 34px;

Diff for: src/i18n/en.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1898,7 +1898,7 @@
18981898
"srtPassManual": "Set Manually",
18991899
"srtPassPlaceholder": "Enter a passphrase",
19001900
"srtPassTooltip": "Set a passphrase to enable encryption\n10-79 alphanumeric character\npassphrase for encryption",
1901-
"hlsStream": "HLS stream URL:",
1901+
"hlsStream": "HLS (Apple HTTP) stream URL:",
19021902
"akamaiStream": "Akamai Stream ID:",
19031903
"transcoding": "Transcoding Profile Configuration",
19041904
"transcodingProfile": "Transcoding Profile:",

0 commit comments

Comments
 (0)