ov-components: add recordingStreamBaseUrl directive and integrate with config service for dynamic stream URL construction

master
Carlos Santos 2025-03-14 18:55:01 +01:00
parent 6137bdbbbc
commit 90fd0ef44e
5 changed files with 96 additions and 7 deletions

View File

@ -36,6 +36,7 @@ import {
MinimalDirective,
ParticipantNameDirective,
PrejoinDirective,
RecordingStreamBaseUrlDirective,
TokenDirective,
TokenErrorDirective,
VideoEnabledDirective
@ -56,6 +57,7 @@ import {
PrejoinDisplayParticipantName,
VideoEnabledDirective,
AudioEnabledDirective,
RecordingStreamBaseUrlDirective,
ToolbarCameraButtonDirective,
ToolbarMicrophoneButtonDirective,
ToolbarScreenshareButtonDirective,
@ -100,6 +102,7 @@ import {
PrejoinDisplayParticipantName,
VideoEnabledDirective,
AudioEnabledDirective,
RecordingStreamBaseUrlDirective,
ToolbarCameraButtonDirective,
ToolbarMicrophoneButtonDirective,
ToolbarScreenshareButtonDirective,

View File

@ -725,3 +725,69 @@ export class AudioEnabledDirective implements OnDestroy {
}
}
}
/**
* The **recordingStreamBaseUrl** directive sets the base URL for retrieving recording streams.
* The complete request URL is dynamically constructed by concatenating the supplied URL, the
* internally managed recordingId, and the `/stream` segment.
*
* The final URL format will be:
*
* {recordingStreamBaseUrl}/{recordingId}/stream
*
* Default: `"/{recordingId}/stream"`
*
* It is essential that the resulting route is declared and configured on your backend, as it is
* used for serving and accessing the recording streams.
*
* @example
* <ov-videoconference [recordingStreamBaseUrl]="'https://myserver.com/api/recordings'">
* </ov-videoconference>
*/
@Directive({
selector: 'ov-videoconference[recordingStreamBaseUrl]'
})
export class RecordingStreamBaseUrlDirective implements AfterViewInit, OnDestroy {
/**
* @ignore
*/
@Input() set recordingStreamBaseUrl(url: string) {
this.update(url);
}
/**
* @ignore
*/
constructor(
private elementRef: ElementRef,
private libService: OpenViduComponentsConfigService
) {}
/**
* @ignore
*/
ngAfterViewInit(): void {
this.update(this.recordingStreamBaseUrl);
}
/**
* @ignore
*/
ngOnDestroy(): void {
this.clear();
}
/**
* @ignore
*/
clear() {
this.update('');
}
/**
* @ignore
*/
update(value: string) {
if (value) this.libService.setRecordingStreamBaseUrl(value);
}
}

View File

@ -33,6 +33,9 @@ export class OpenViduComponentsConfigService {
private audioEnabled = <BehaviorSubject<boolean>>new BehaviorSubject(true);
audioEnabled$: Observable<boolean>;
private recordingStreamBaseUrl = <BehaviorSubject<string>>new BehaviorSubject('');
recordingStreamBaseUrl$: Observable<string>;
//Toolbar settings
private cameraButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
cameraButton$: Observable<boolean>;
@ -121,6 +124,7 @@ export class OpenViduComponentsConfigService {
this.prejoinDisplayParticipantName$ = this.prejoinDisplayParticipantName.asObservable();
this.videoEnabled$ = this.videoEnabled.asObservable();
this.audioEnabled$ = this.audioEnabled.asObservable();
this.recordingStreamBaseUrl$ = this.recordingStreamBaseUrl.asObservable();
//Toolbar observables
this.cameraButton$ = this.cameraButton.asObservable();
this.microphoneButton$ = this.microphoneButton.asObservable();
@ -214,6 +218,17 @@ export class OpenViduComponentsConfigService {
return this.audioEnabled.getValue();
}
setRecordingStreamBaseUrl(recordingStreamBaseUrl: string) {
this.recordingStreamBaseUrl.next(recordingStreamBaseUrl);
}
getRecordingStreamBaseUrl(): string {
let baseUrl = this.recordingStreamBaseUrl.getValue();
// Add trailing slash if not present
baseUrl += baseUrl.endsWith('/') ? '' : '/';
return baseUrl;
}
//Toolbar settings
setCameraButton(cameraButton: boolean) {

View File

@ -2,7 +2,9 @@ import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { RecordingInfo, RecordingStatus, RecordingStatusInfo } from '../../models/recording.model';
import { ActionService } from '../action/action.service';
import { GlobalConfigService } from '../config/global-config.service';
import { LoggerService } from '../logger/logger.service';
import { ILogger } from '../../models/logger.model';
import { OpenViduComponentsConfigService } from '../config/directive-config.service';
@Injectable({
providedIn: 'root'
@ -13,19 +15,19 @@ export class RecordingService {
*/
recordingStatusObs: Observable<RecordingStatusInfo>;
private recordingTimeInterval: NodeJS.Timeout;
private API_RECORDINGS_PREFIX = 'call/api/recordings/';
private recordingStatus = <BehaviorSubject<RecordingStatusInfo>>new BehaviorSubject({
status: RecordingStatus.STOPPED,
recordingList: [] as RecordingInfo[],
recordingElapsedTime: new Date(0, 0, 0, 0, 0, 0, 0)
});
private log: ILogger;
/**
* @internal
*/
constructor(private actionService: ActionService, private globalService: GlobalConfigService) {
constructor(private actionService: ActionService, private libService: OpenViduComponentsConfigService, private loggerService: LoggerService) {
this.log = this.loggerService.get('RecordingService');
this.recordingStatusObs = this.recordingStatus.asObservable();
this.API_RECORDINGS_PREFIX = this.globalService.getBaseHref() + this.API_RECORDINGS_PREFIX;
}
/**
@ -146,9 +148,10 @@ export class RecordingService {
*/
playRecording(recording: RecordingInfo) {
// Only COMPOSED recording is supported. The extension will allways be 'mp4'.
console.log('Playing recording', recording);
this.log.d('Playing recording', recording);
const queryParamForAvoidCache = `?t=${new Date().getTime()}`;
const streamRecordingUrl = `${this.API_RECORDINGS_PREFIX}${recording.id}/stream${queryParamForAvoidCache}`;
const baseUrl = this.libService.getRecordingStreamBaseUrl();
const streamRecordingUrl = `${baseUrl}${recording.id}/stream${queryParamForAvoidCache}`;
this.actionService.openRecordingPlayerDialog(streamRecordingUrl);
}
@ -161,7 +164,8 @@ export class RecordingService {
// Only COMPOSED recording is supported. The extension will allways be 'mp4'.
const queryParamForAvoidCache = `?t=${new Date().getTime()}`;
const link = document.createElement('a');
link.href = `${this.API_RECORDINGS_PREFIX}${recording.id}/stream${queryParamForAvoidCache}`;
const baseUrl = this.libService.getRecordingStreamBaseUrl();
link.href = `${baseUrl}${recording.id}/stream${queryParamForAvoidCache}`;
link.download = recording.filename || 'openvidu-recording.mp4';
link.dispatchEvent(
new MouseEvent('click', {

View File

@ -11,6 +11,7 @@
[participantName]="'Participant'"
[videoEnabled]="true"
[audioEnabled]="true"
[recordingStreamBaseUrl]="'call/api/recordings'"
[toolbarCameraButton]="true"
[toolbarMicrophoneButton]="true"
[toolbarScreenshareButton]="true"