diff --git a/openvidu-browser/src/OpenVidu/Publisher.ts b/openvidu-browser/src/OpenVidu/Publisher.ts index b063c949..c46e24be 100644 --- a/openvidu-browser/src/OpenVidu/Publisher.ts +++ b/openvidu-browser/src/OpenVidu/Publisher.ts @@ -86,10 +86,6 @@ export class Publisher extends StreamManager { * @hidden */ screenShareResizeInterval: NodeJS.Timer; - /** - * @hidden - */ - IEAdapter: any; /** * @hidden diff --git a/openvidu-browser/src/OpenVidu/StreamManager.ts b/openvidu-browser/src/OpenVidu/StreamManager.ts index d6036998..08e5825f 100644 --- a/openvidu-browser/src/OpenVidu/StreamManager.ts +++ b/openvidu-browser/src/OpenVidu/StreamManager.ts @@ -24,6 +24,7 @@ import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent' import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode'; import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger'; import { PlatformUtils } from '../OpenViduInternal/Utils/Platform'; +import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent'; /** * @hidden @@ -90,19 +91,23 @@ export class StreamManager extends EventDispatcher { /** * @hidden */ - firstVideoElement?: StreamManagerVideo; + protected firstVideoElement?: StreamManagerVideo; /** * @hidden */ - lazyLaunchVideoElementCreatedEvent = false; - /** - * @hidden - */ - element: HTMLElement; + protected element: HTMLElement; /** * @hidden */ protected canPlayListener: EventListener; + /** + * @hidden + */ + private streamPlayingEventExceptionTimeout?: NodeJS.Timeout; + /** + * @hidden + */ + private lazyLaunchVideoElementCreatedEvent = false; /** * @hidden @@ -138,6 +143,7 @@ export class StreamManager extends EventDispatcher { } this.canPlayListener = () => { + this.deactivateStreamPlayingEventExceptionTimeout(); if (this.stream.isLocal()) { if (!this.stream.displayMyRemote()) { logger.info("Your local 'Stream' with id [" + this.stream.streamId + '] video is now playing'); @@ -305,7 +311,7 @@ export class StreamManager extends EventDispatcher { id: video.id, canplayListenerAdded: false }); - + logger.info('New video element associated to ', this); return returnNumber; @@ -492,6 +498,7 @@ export class StreamManager extends EventDispatcher { */ addPlayEventToFirstVideo() { if ((!!this.videos[0]) && (!!this.videos[0].video) && (!this.videos[0].canplayListenerAdded)) { + this.activateStreamPlayingEventExceptionTimeout(); this.videos[0].video.addEventListener('canplay', this.canPlayListener); this.videos[0].canplayListenerAdded = true; } @@ -533,6 +540,7 @@ export class StreamManager extends EventDispatcher { */ removeSrcObject(streamManagerVideo: StreamManagerVideo) { streamManagerVideo.video.srcObject = null; + this.deactivateStreamPlayingEventExceptionTimeout(); } /* Private methods */ @@ -557,4 +565,24 @@ export class StreamManager extends EventDispatcher { video.style.webkitTransform = 'unset'; } + private activateStreamPlayingEventExceptionTimeout() { + if (this.streamPlayingEventExceptionTimeout != null) { + // The timeout is already activated + return; + } + // Trigger ExceptionEvent NO_STREAM_PLAYING_EVENT if after timeout there is no 'canplay' event + const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000; + this.streamPlayingEventExceptionTimeout = setTimeout(() => { + const msg = 'StreamManager of Stream ' + this.stream.streamId + ' (' + (this.remote ? 'Subscriber' : 'Publisher') + ') did not trigger "streamPlaying" event in ' + msTimeout + ' ms'; + logger.warn(msg); + this.emitEvent('exception', [new ExceptionEvent(this.stream.session, ExceptionEventName.NO_STREAM_PLAYING_EVENT, this, msg)]); + delete this.streamPlayingEventExceptionTimeout; + }, msTimeout); + } + + private deactivateStreamPlayingEventExceptionTimeout() { + clearTimeout(this.streamPlayingEventExceptionTimeout as any); + delete this.streamPlayingEventExceptionTimeout; + } + } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Events/ExceptionEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/ExceptionEvent.ts index 8c086fa7..c7cb9b72 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/ExceptionEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/ExceptionEvent.ts @@ -17,6 +17,7 @@ import { Session } from '../../OpenVidu/Session'; import { Stream } from '../../OpenVidu/Stream'; +import { StreamManager } from '../../OpenVidu/StreamManager'; import { Event } from './Event'; @@ -36,7 +37,9 @@ export enum ExceptionEventName { * The [ICE connection state](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState) * of an [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) reached `failed` status. * - * This is a terminal error that won't have any kind of possible recovery. + * This is a terminal error that won't have any kind of possible recovery. If the client is still connected to OpenVidu Server, + * then an automatic reconnection process of the media stream is immediately performed. If the ICE connection has broken due to + * a total network drop, then no automatic reconnection process will be possible. * * [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Stream]] object. */ @@ -46,12 +49,38 @@ export enum ExceptionEventName { * The [ICE connection state](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState) * of an [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) reached `disconnected` status. * - * This is not a terminal error, and it is possible for the ICE connection to be reconnected. + * This is not a terminal error, and it is possible for the ICE connection to be reconnected. If the client is still connected to + * OpenVidu Server and after certain timeout the ICE connection has not reached a success or terminal status, then an automatic + * reconnection process of the media stream is performed. If the ICE connection has broken due to a total network drop, then no + * automatic reconnection process will be possible. + * + * You can customize the timeout for the reconnection attempt with property [[OpenViduAdvancedConfiguration.iceConnectionDisconnectedExceptionTimeout]], + * which by default is 4000 milliseconds. * * [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Stream]] object. */ - ICE_CONNECTION_DISCONNECTED = 'ICE_CONNECTION_DISCONNECTED' + ICE_CONNECTION_DISCONNECTED = 'ICE_CONNECTION_DISCONNECTED', + /** + * A [[StreamManager]] object has not fired event `streamPlaying` after certain timeout. `streamPlaying` event belongs to [[StreamManagerEvent]] + * category. It wraps Web API native event [canplay](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event). + * + * OpenVidu Browser can take care of the video players (see [here](/en/latest/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)), + * or you can take care of video players on your own (see [here](/en/latest/cheatsheet/manage-videos/#you-take-care-of-the-video-players)). + * Either way, whenever a [[Publisher]] or [[Subscriber]] object is commanded to attach its [[Stream]] to a video element, it is supposed to fire + * `streamPlaying` event shortly after. If it does not, then we can safely assume that something wrong has happened and the application may be notified + * through this specific ExceptionEvent. + * + * The timeout can be configured with property [[OpenViduAdvancedConfiguration.noStreamPlayingEventExceptionTimeout]]. By default it is 4000 milliseconds. + * + * This is just an informative exception. It only means that a Stream that is supposed to be playing by a video player has not done so + * in a reasonable time. But the lack of the event can be caused by multiple reasons. If a Subscriber is not playing its Stream, the origin + * of the problem could be located at the Publisher side. Or may be caused by a transient network problem. But it also could be a problem with + * autoplay permissions. Bottom line, the cause can be very varied, and depending on the application the lack of the event could even be expected. + * + * [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[StreamManager]] object. + */ + NO_STREAM_PLAYING_EVENT = 'NO_STREAM_PLAYING_EVENT' } /** @@ -70,8 +99,9 @@ export class ExceptionEvent extends Event { * Object affected by the exception. Depending on the [[ExceptionEvent.name]] property: * - [[Session]]: `ICE_CANDIDATE_ERROR` * - [[Stream]]: `ICE_CONNECTION_FAILED`, `ICE_CONNECTION_DISCONNECTED` + * - [[StreamManager]]: `NO_STREAM_PLAYING_EVENT` */ - origin: Session | Stream; + origin: Session | Stream | StreamManager; /** * Informative description of the exception @@ -86,7 +116,7 @@ export class ExceptionEvent extends Event { /** * @hidden */ - constructor(session: Session, name: ExceptionEventName, origin: Session | Stream, message: string, data?: any) { + constructor(session: Session, name: ExceptionEventName, origin: Session | Stream | StreamManager, message: string, data?: any) { super(false, session, 'exception'); this.name = name; this.origin = origin; diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts index 78225898..ddfe9e77 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts @@ -21,7 +21,10 @@ import { StreamManager } from '../../OpenVidu/StreamManager'; /** * Defines the following events: * - `streamPlaying`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) whenever its media stream starts playing (one of its videos has media - * and has begun to play). This event will be dispatched when these 3 conditions are met 1) The StreamManager has no video associated in the DOM 2) It is associated to one video 3) That video starts playing + * and has begun to play). This event will be dispatched when these 3 conditions are met: + * 1. The StreamManager has no video associated in the DOM + * 2. It is associated to one video + * 3. That video starts playing. Internally the expected Web API event is [HTMLMediaElement.canplay](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event) * - `streamAudioVolumeChange`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) when the volume of its Stream's audio track * changes. Only applies if [[Stream.hasAudio]] is `true`. The frequency this event is fired with is defined by property `interval` of * [[OpenViduAdvancedConfiguration.publisherSpeakingEventsOptions]] (default 100ms) diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration.ts index 1233f1fa..cc69c45d 100644 --- a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration.ts +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration.ts @@ -52,4 +52,20 @@ export interface OpenViduAdvancedConfiguration { */ forceMediaReconnectionAfterNetworkDrop?: boolean; -} + /** + * The milliseconds that must elapse after triggering [[ExceptionEvent]] of type `ICE_CONNECTION_DISCONNECTED` to perform an automatic reconnection process of the affected media stream. + * This automatic reconnection process can only take place if the client still has network connection to OpenVidu Server. If the ICE connection has broken because of a total network drop, + * then no reconnection process will be possible at all. + * + * Default to `4000`. + */ + iceConnectionDisconnectedExceptionTimeout?: number; + + /** + * The milliseconds that must elapse for the [[ExceptionEvent]] of name [`NO_STREAM_PLAYING_EVENT`]((/en/latest/api/openvidu-browser/enums/exceptioneventname.html#no_stream_playing_event)) to be fired. + * + * Default to `4000`. + */ + noStreamPlayingEventExceptionTimeout?: number; + +} \ No newline at end of file