From e884225fdd87d8394c044c7bd8738dcaf46be81d Mon Sep 17 00:00:00 2001 From: MathieuPoux Date: Wed, 25 Sep 2024 16:23:53 +0200 Subject: [PATCH] feat: add a streamState and fix streamMetadataError --- index.ts | 2 +- src/Player.ts | 29 +++++++++++++++++++-- src/metadata/StreamMetadata.ts | 47 +++++++++++++++++++++++++++++++--- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/index.ts b/index.ts index 444dcb3..707b36c 100644 --- a/index.ts +++ b/index.ts @@ -25,7 +25,7 @@ export { IStats } from './src/stats/IStats'; export { StreamerStats } from './src/stats/StreamerStats'; // Metadata export { Metadata, MSource, MTrack, MType } from './src/metadata/Metadata'; -export { StreamMetadata, StreamMetadataError } from './src/metadata/StreamMetadata'; +export { StreamState, StreamMetadata, StreamMetadataError } from './src/metadata/StreamMetadata'; // Timed metadata export { IStreamData } from './src/metadata/IStreamData'; export { WSStreamData } from './src/metadata/WSStreamData'; diff --git a/src/Player.ts b/src/Player.ts index 237ba4c..e1c5603 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -4,7 +4,7 @@ * See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details. */ -import { StreamMetadata, StreamMetadataError } from './metadata/StreamMetadata'; +import { StreamMetadata, StreamMetadataError, StreamState } from './metadata/StreamMetadata'; import { ILog, Connect, Util, EventEmitter, WebSocketReliableError } from '@ceeblue/web-utils'; import { ConnectionInfos, ConnectorError, IConnector } from './connectors/IConnector'; import { IController, IsController, PlayingInfos } from './connectors/IController'; @@ -91,6 +91,14 @@ export class Player extends EventEmitter { } } + /** + * Event fired when stream state is changing + * @param state + */ + onState(state: StreamState) { + this.log('onState', state).info(); + } + /** * Event fired every second to report information while content plays * @param playing @@ -157,6 +165,13 @@ export class Player extends EventEmitter { return this._connector; } + /** + * State of the stream as indicated by the server + */ + get streamState(): StreamState { + return this._streamMetadata?.streamState || StreamState.UNKNOWN; + } + /** * Returns the {@link Metadata} object description */ @@ -383,7 +398,16 @@ export class Player extends EventEmitter { // reset to release resources! mbr?.reset(); // Stop the player if signaling fails! - this.stop(error); + if (this.streamState === StreamState.OFFLINE) { + // if stream-state was offine on disconnection, shows this error! + this.stop({ + type: 'StreamMetadataError', + name: this.streamState, + stream: (params as Connect.Params).streamName + }); + } else { + this.stop(error); + } }; // Timed Metadatas @@ -518,6 +542,7 @@ export class Player extends EventEmitter { private _initStreamMetadata(params: Connect.Params, streamMetadata: StreamMetadata) { this._streamMetadata = streamMetadata; streamMetadata.log = this.log.bind(this, 'StreamMetadata:') as ILog; + streamMetadata.onState = (state: StreamState) => this.onState(state); streamMetadata.onMetadata = metadata => { if (!this._connector || !this._connector.opened) { return; diff --git a/src/metadata/StreamMetadata.ts b/src/metadata/StreamMetadata.ts index e8c6b26..dd0d236 100644 --- a/src/metadata/StreamMetadata.ts +++ b/src/metadata/StreamMetadata.ts @@ -9,6 +9,15 @@ import { MTrack, MType, Metadata } from './Metadata'; const sortByMAXBPS = (track1: MTrack, track2: MTrack) => track2.maxbps - track1.maxbps; +export enum StreamState { + UNKNOWN = '', + ONLINE = 'Stream is online', + OFFLINE = 'Stream is offline', + LOADING = 'Stream is initializing', + BOOTING = 'Stream is booting', + WAITING = 'Stream is waiting for data' +} + export type StreamMetadataError = /** * Represents a Connection error. @@ -18,7 +27,6 @@ export type StreamMetadataError = * Represents a {@link WebSocketReliableError} error */ | WebSocketReliableError; - /** * Use StreamMetadata to get real-time information on a server stream, including: * - the list of tracks and their properties, @@ -28,8 +36,16 @@ export type StreamMetadataError = * streamMetadata.onMetadata = metadata => { * console.log(metadata); * } + * */ export class StreamMetadata extends EventEmitter { + /** + * Event fired when stream state is changing + * @param state + */ + onState(state: StreamState) { + this.log('onState', state).info(); + } /** * Event fired when the stream is closed * @param error error description on an improper closure @@ -55,6 +71,13 @@ export class StreamMetadata extends EventEmitter { return this._ws.url; } + /** + * State of the stream as indicated by the server + */ + get streamState(): StreamState { + return this._streamState; + } + /** * Returns the {@link Connect.Params} object containing the connection parameters */ @@ -78,24 +101,40 @@ export class StreamMetadata extends EventEmitter { private _ws: WebSocketReliable; private _metadata?: Metadata; private _connectParams: Connect.Params; + private _streamState: StreamState; /** * Create a new StreamMetadata instance, connects to the server using WebSocket and * listen to metadata events. */ constructor(connectParams: Connect.Params) { super(); + + const states = new Map(); + for (const state of Object.values(StreamState)) { + states.set(state, state); + } + // Server can server the following text to indicate a unknown status + states.set('Stream status is unknown?!', StreamState.UNKNOWN); + this._connectParams = connectParams; + this._streamState = StreamState.UNKNOWN; this._ws = new WebSocketReliable(Connect.buildURL(Connect.Type.META, connectParams)); this._ws.onClose = (error?: WebSocketReliableError) => this.close(error); this._ws.onMessage = (message: string) => { try { const data = JSON.parse(message); if (data.error) { - // Unrecoverable issue! - this.close({ type: 'StreamMetadataError', name: data.error, stream: connectParams.streamName }); + const state = states.get(data.error); + if (state) { + this.onState((this._streamState = state)); + } else { + // Unrecoverable issue! + this.close({ type: 'StreamMetadataError', name: data.error, stream: connectParams.streamName }); + } return; } + // Metadata this._metadata = new Metadata(); this._metadata.type = data.type; this._metadata.width = data.width; @@ -135,6 +174,8 @@ export class StreamMetadata extends EventEmitter { for (const track of tracks) { this._metadata.tracks.set(track.idx, track); } + // SUCCESS + this.onState((this._streamState = StreamState.ONLINE)); } catch (e) { this.log(Util.stringify(e)).error(); return;