UNPKG

@ngx-telly/plugin-wowza-webrtc

Version:
246 lines (240 loc) 9.1 kB
import * as i0 from '@angular/core'; import { inject, Input, Directive } from '@angular/core'; import { PlayerComponent } from '@ngx-telly/player'; /* * This code and all components (c) Copyright 2019-2020, Wowza Media Systems, LLC. All rights reserved. * This code is licensed pursuant to the BSD 3-Clause License. */ function mungeSDPPlay(sdpStr) { // For greatest playback compatibility, // force H.264 playback to constrained baseline (42e01f). const sdpLines = sdpStr.split(/\r\n/); let sdpStrRet = ''; for (const sdpIndex in sdpLines) { let sdpLine = sdpLines[sdpIndex]; if (sdpLine.length == 0) continue; if (sdpLine.includes('profile-level-id')) { // The profile-level-id string has three parts: XXYYZZ, where // XX: 42 baseline, 4D main, 64 high // YY: constraint // ZZ: level ID // Look for codecs higher than baseline and force downward. const profileLevelId = sdpLine.substr(sdpLine.indexOf('profile-level-id') + 17, 6); let profile = Number('0x' + profileLevelId.substr(0, 2)); let constraint = Number('0x' + profileLevelId.substr(2, 2)); let level = Number('0x' + profileLevelId.substr(4, 2)); if (profile > 0x42) { profile = 0x42; constraint = 0xe0; level = 0x1f; } if (constraint == 0x00) { constraint = 0xe0; } const newProfileLevelId = ('00' + profile.toString(16)).slice(-2).toLowerCase() + ('00' + constraint.toString(16)).slice(-2).toLowerCase() + ('00' + level.toString(16)).slice(-2).toLowerCase(); sdpLine = sdpLine.replace(profileLevelId, newProfileLevelId); } sdpStrRet += sdpLine; sdpStrRet += '\r\n'; } return sdpStrRet; } class WowzaConnector { constructor(config) { this.onIceCandidateRcv = (ev) => { console.log('[ ICE ]', ev.candidate); }; this.onDescriptionRcv = (desc) => { this.peerConnection ?.setLocalDescription(desc) .then(() => { this.wsSend('sendResponse', { sdp: desc }); }) .catch((err) => { console.log('[WEB RTC] Setting local RTCSessionDescriptionInit failed', err); }); }; this.onTrackRcv = (ev) => { try { this.config.videoEl.srcObject = ev.streams[0]; } catch { console.log('[WEB RTC] Cannot bind srcObject to video element. Sources: ', ev.streams); } }; this.onMsgRcv = (ev) => { const msg = JSON.parse(ev.data); const status = +msg['status']; const command = msg['command']; switch (status) { case 514: { let retries = 0; if (retries < 10) { setTimeout(() => { this.wsSend('getOffer'); retries++; }, 500); } else { this.stop(); } break; } case 200: { if (msg['streamInfo']) { const streamInfo = msg['streamInfo']; this.config.streamInfo.sessionId = streamInfo.sessionId; } if (msg['sdp']) { msg.sdp.sdp = mungeSDPPlay(msg.sdp.sdp); this.peerConnection?.setRemoteDescription(new RTCSessionDescription(msg.sdp)).then(() => { this.peerConnection?.createAnswer().then((d) => this.onDescriptionRcv(d)); }); } if (msg['iceCandidates'] && msg['iceCandidates'].length) { msg['iceCandidates'].forEach((x) => { this.peerConnection?.addIceCandidate(new RTCIceCandidate(x)); }); } if ('sendResponse'.localeCompare(command) == 0) { this.wsConnection?.close(); this.wsConnection = undefined; } if ('getAvailableStreams'.localeCompare(command) == 0) { this.stop(); } break; } default: { this.stop(); break; } } }; this.onStateChanged = (ev) => { this.config?.stateCallback?.(ev); }; this.onError = (ev) => { this.config?.errorCallback?.(ev); }; this.onClose = (ev) => { this.config?.closeCallback?.(ev); }; this.onWsConnected = (c) => { this.peerConnection = new RTCPeerConnection(); this.peerConnection.onicecandidate = this.onIceCandidateRcv; this.peerConnection.ontrack = this.onTrackRcv; this.peerConnection.onconnectionstatechange = this.onStateChanged; this.wsSend('getOffer'); }; this.config = config; } start() { if (!this.peerConnection) { this.wsConnect(this.config.sdpUrl); } } stop() { if (this.peerConnection) { this.peerConnection.close(); this.peerConnection = undefined; } if (this.wsConnection) { this.wsConnection.close(); this.wsConnection = undefined; } if (this.config.videoEl) { this.config.videoEl.src = ''; } } wsConnect(url) { this.wsConnection = new WebSocket(url); this.wsConnection.binaryType = 'arraybuffer'; this.wsConnection.onopen = this.onWsConnected; this.wsConnection.onmessage = this.onMsgRcv; this.wsConnection.onclose = this.onClose; this.wsConnection.onerror = this.onError; } wsSend(command, ext = {}) { const cmd = { direction: 'play', command: command, streamInfo: this.config.streamInfo, userData: this.config.userData, ...ext, }; if (this.config.secureToken != null) { cmd.secureToken = this.config.secureToken; } this.wsConnection?.send(JSON.stringify(cmd)); } } class WowzaDirective { constructor() { this.sessionId = '[empty]'; this.player = inject(PlayerComponent); } ngOnChanges(changes) { if (changes['tellyWowza']?.currentValue || changes['streamName']?.currentValue) { this.create(); } } create() { if (!this.tellyWowza || !this.streamName || !this.appName) { return; } if (this.wowza) { this.destroy(); } const config = { sdpUrl: this.tellyWowza, videoEl: this.player.video.nativeElement, streamInfo: { applicationName: this.appName, streamName: this.streamName, sessionId: this.sessionId, }, }; if (this.token != null) { config.secureToken = this.token; } this.wowza = new WowzaConnector(config); this.wowza.start(); } destroy() { this.player.video.nativeElement.srcObject = null; this.wowza?.stop(); this.wowza = undefined; } ngOnDestroy() { this.destroy(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: WowzaDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.9", type: WowzaDirective, isStandalone: true, selector: "[tellyWowza]", inputs: { tellyWowza: "tellyWowza", streamName: "streamName", appName: "appName", sessionId: "sessionId", token: "token" }, usesOnChanges: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: WowzaDirective, decorators: [{ type: Directive, args: [{ selector: '[tellyWowza]', standalone: true, }] }], propDecorators: { tellyWowza: [{ type: Input, args: [{ required: true }] }], streamName: [{ type: Input }], appName: [{ type: Input }], sessionId: [{ type: Input }], token: [{ type: Input }] } }); /** * Generated bundle index. Do not edit. */ export { WowzaConnector, WowzaDirective }; //# sourceMappingURL=ngx-telly-plugin-wowza-webrtc.mjs.map