diff --git a/index.d.ts b/index.d.ts index 5755b7e15a..8881197e48 100644 --- a/index.d.ts +++ b/index.d.ts @@ -3113,8 +3113,6 @@ export interface AbrController { setPlaybackQuality(type: string, streamInfo: StreamInfo, representation: Representation, reason: object): void; - setWindowResizeEventCalled(value: any): void; - unRegisterStreamType(streamId: string, type: string): void; } diff --git a/src/core/events/CoreEvents.js b/src/core/events/CoreEvents.js index 8bffb0aa56..60ee8f5305 100644 --- a/src/core/events/CoreEvents.js +++ b/src/core/events/CoreEvents.js @@ -43,8 +43,8 @@ class CoreEvents extends EventsBase { this.ATTEMPT_BACKGROUND_SYNC = 'attemptBackgroundSync'; this.BUFFERING_COMPLETED = 'bufferingCompleted'; this.BUFFER_CLEARED = 'bufferCleared'; - this.BYTES_APPENDED_END_FRAGMENT = 'bytesAppendedEndFragment'; this.BUFFER_REPLACEMENT_STARTED = 'bufferReplacementStarted'; + this.BYTES_APPENDED_END_FRAGMENT = 'bytesAppendedEndFragment'; this.CHECK_FOR_EXISTENCE_COMPLETED = 'checkForExistenceCompleted'; this.CMSD_STATIC_HEADER = 'cmsdStaticHeader'; this.CURRENT_TRACK_CHANGED = 'currentTrackChanged'; @@ -54,22 +54,31 @@ class CoreEvents extends EventsBase { this.INIT_FRAGMENT_LOADED = 'initFragmentLoaded'; this.INIT_FRAGMENT_NEEDED = 'initFragmentNeeded'; this.INTERNAL_MANIFEST_LOADED = 'internalManifestLoaded'; - this.ORIGINAL_MANIFEST_LOADED = 'originalManifestLoaded'; + this.LOADING_ABANDONED = 'loadingAborted'; this.LOADING_COMPLETED = 'loadingCompleted'; - this.LOADING_PROGRESS = 'loadingProgress'; this.LOADING_DATA_PROGRESS = 'loadingDataProgress'; - this.LOADING_ABANDONED = 'loadingAborted'; + this.LOADING_PROGRESS = 'loadingProgress'; this.MANIFEST_UPDATED = 'manifestUpdated'; + this.MEDIAINFO_UPDATED = 'mediaInfoUpdated'; this.MEDIA_FRAGMENT_LOADED = 'mediaFragmentLoaded'; this.MEDIA_FRAGMENT_NEEDED = 'mediaFragmentNeeded'; - this.MEDIAINFO_UPDATED = 'mediaInfoUpdated'; + this.ORIGINAL_MANIFEST_LOADED = 'originalManifestLoaded'; this.QUOTA_EXCEEDED = 'quotaExceeded'; + this.SEEK_TARGET = 'seekTarget'; this.SEGMENT_LOCATION_BLACKLIST_ADD = 'segmentLocationBlacklistAdd'; this.SEGMENT_LOCATION_BLACKLIST_CHANGED = 'segmentLocationBlacklistChanged'; this.SERVICE_LOCATION_BASE_URL_BLACKLIST_ADD = 'serviceLocationBlacklistAdd'; this.SERVICE_LOCATION_BASE_URL_BLACKLIST_CHANGED = 'serviceLocationBlacklistChanged'; this.SERVICE_LOCATION_LOCATION_BLACKLIST_ADD = 'serviceLocationLocationBlacklistAdd'; this.SERVICE_LOCATION_LOCATION_BLACKLIST_CHANGED = 'serviceLocationLocationBlacklistChanged'; + this.SETTING_UPDATED_ABR_ACTIVE_RULES = 'settingUpdatedAbrActiveRules'; + this.SETTING_UPDATED_CATCHUP_ENABLED = 'settingUpdatedCatchupEnabled'; + this.SETTING_UPDATED_LIVE_DELAY = 'settingUpdatedLiveDelay'; + this.SETTING_UPDATED_LIVE_DELAY_FRAGMENT_COUNT = 'settingUpdatedLiveDelayFragmentCount'; + this.SETTING_UPDATED_MAX_BITRATE = 'settingUpdatedMaxBitrate'; + this.SETTING_UPDATED_MIN_BITRATE = 'settingUpdatedMinBitrate'; + this.SETTING_UPDATED_PLAYBACK_RATE_MAX = 'settingUpdatedPlaybackRateMax'; + this.SETTING_UPDATED_PLAYBACK_RATE_MIN = 'settingUpdatedPlaybackRateMin'; this.SET_FRAGMENTED_TEXT_AFTER_DISABLED = 'setFragmentedTextAfterDisabled'; this.SET_NON_FRAGMENTED_TEXT = 'setNonFragmentedText'; this.SOURCE_BUFFER_ERROR = 'sourceBufferError'; @@ -81,18 +90,10 @@ class CoreEvents extends EventsBase { this.UPDATE_TIME_SYNC_OFFSET = 'updateTimeSyncOffset'; this.URL_RESOLUTION_FAILED = 'urlResolutionFailed'; this.VIDEO_CHUNK_RECEIVED = 'videoChunkReceived'; + this.VIDEO_ELEMENT_RESIZED = 'videoElementResized'; this.WALLCLOCK_TIME_UPDATED = 'wallclockTimeUpdated'; this.XLINK_ELEMENT_LOADED = 'xlinkElementLoaded'; this.XLINK_READY = 'xlinkReady'; - this.SEEK_TARGET = 'seekTarget'; - this.SETTING_UPDATED_LIVE_DELAY = 'settingUpdatedLiveDelay'; - this.SETTING_UPDATED_LIVE_DELAY_FRAGMENT_COUNT = 'settingUpdatedLiveDelayFragmentCount'; - this.SETTING_UPDATED_CATCHUP_ENABLED = 'settingUpdatedCatchupEnabled'; - this.SETTING_UPDATED_PLAYBACK_RATE_MIN = 'settingUpdatedPlaybackRateMin'; - this.SETTING_UPDATED_PLAYBACK_RATE_MAX = 'settingUpdatedPlaybackRateMax'; - this.SETTING_UPDATED_ABR_ACTIVE_RULES = 'settingUpdatedAbrActiveRules'; - this.SETTING_UPDATED_MAX_BITRATE = 'settingUpdatedMaxBitrate'; - this.SETTING_UPDATED_MIN_BITRATE = 'settingUpdatedMinBitrate'; } } diff --git a/src/streaming/controllers/AbrController.js b/src/streaming/controllers/AbrController.js index a284a15235..c631933ca0 100644 --- a/src/streaming/controllers/AbrController.js +++ b/src/streaming/controllers/AbrController.js @@ -72,8 +72,7 @@ function AbrController() { streamProcessorDict, switchRequestHistory, throughputController, - videoModel, - windowResizeEventCalled; + videoModel function setup() { logger = debug.getLogger(instance); @@ -97,6 +96,7 @@ function AbrController() { eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_RENDERED, _onQualityChangeRendered, instance); eventBus.on(MediaPlayerEvents.METRIC_ADDED, _onMetricAdded, instance); eventBus.on(Events.LOADING_PROGRESS, _onFragmentLoadProgress, instance); + eventBus.on(Events.VIDEO_ELEMENT_RESIZED, _onVideoElementResized, instance); } /** @@ -152,9 +152,6 @@ function AbrController() { streamProcessorDict = {}; queuedManualQualitySwitches = new Map(); - if (windowResizeEventCalled === undefined) { - windowResizeEventCalled = false; - } if (droppedFramesHistory) { droppedFramesHistory.reset(); } @@ -177,6 +174,7 @@ function AbrController() { eventBus.off(MediaPlayerEvents.QUALITY_CHANGE_RENDERED, _onQualityChangeRendered, instance); eventBus.off(MediaPlayerEvents.METRIC_ADDED, _onMetricAdded, instance); eventBus.off(Events.LOADING_PROGRESS, _onFragmentLoadProgress, instance); + eventBus.off(Events.VIDEO_ELEMENT_RESIZED, _onVideoElementResized, instance); if (abrRulesCollection) { abrRulesCollection.reset(); @@ -563,6 +561,16 @@ function AbrController() { } } + function _onVideoElementResized() { + if (settings.get().streaming.abr.limitBitrateByPortal) { + Object.keys(streamProcessorDict).forEach(streamId => { + Object.keys(streamProcessorDict[streamId]).forEach(mediaType => { + checkPlaybackQuality(mediaType, streamId); + }); + }); + } + } + function _createRulesContext(streamProcessor, currentRequest) { return RulesContext(context).create({ abrController: instance, @@ -976,10 +984,6 @@ function AbrController() { return voRepresentations[voRepresentations.length - 1].id === representation.id; } - function setWindowResizeEventCalled(value) { - windowResizeEventCalled = value; - } - function clearDataForStream(streamId) { if (droppedFramesHistory) { droppedFramesHistory.clearForStream(streamId); @@ -1022,7 +1026,6 @@ function AbrController() { reset, setConfig, setPlaybackQuality, - setWindowResizeEventCalled, unRegisterStreamType, }; diff --git a/src/streaming/controllers/PlaybackController.js b/src/streaming/controllers/PlaybackController.js index af9a942c00..97f1969565 100644 --- a/src/streaming/controllers/PlaybackController.js +++ b/src/streaming/controllers/PlaybackController.js @@ -878,41 +878,41 @@ function PlaybackController() { function addAllListeners() { videoModel.addEventListener('canplay', _onCanPlay); videoModel.addEventListener('canplaythrough', _onCanPlayThrough); + videoModel.addEventListener('ended', _onNativePlaybackEnded); + videoModel.addEventListener('error', _onPlaybackError); + videoModel.addEventListener('loadeddata', _onPlaybackLoadedData); + videoModel.addEventListener('loadedmetadata', _onPlaybackMetaDataLoaded); + videoModel.addEventListener('pause', _onPlaybackPaused); videoModel.addEventListener('play', _onPlaybackStart); - videoModel.addEventListener('waiting', _onPlaybackWaiting); videoModel.addEventListener('playing', _onPlaybackPlaying); - videoModel.addEventListener('pause', _onPlaybackPaused); - videoModel.addEventListener('error', _onPlaybackError); - videoModel.addEventListener('seeking', _onPlaybackSeeking); - videoModel.addEventListener('seeked', _onPlaybackSeeked); - videoModel.addEventListener('timeupdate', _onPlaybackTimeUpdated); videoModel.addEventListener('progress', _onPlaybackProgress); videoModel.addEventListener('ratechange', _onPlaybackRateChanged); - videoModel.addEventListener('loadedmetadata', _onPlaybackMetaDataLoaded); - videoModel.addEventListener('loadeddata', _onPlaybackLoadedData); + videoModel.addEventListener('seeked', _onPlaybackSeeked); + videoModel.addEventListener('seeking', _onPlaybackSeeking); videoModel.addEventListener('stalled', onPlaybackStalled); - videoModel.addEventListener('ended', _onNativePlaybackEnded); + videoModel.addEventListener('timeupdate', _onPlaybackTimeUpdated); videoModel.addEventListener('volumechange', _onVolumeChanged); + videoModel.addEventListener('waiting', _onPlaybackWaiting); } function removeAllListeners() { videoModel.removeEventListener('canplay', _onCanPlay); videoModel.removeEventListener('canplaythrough', _onCanPlayThrough); + videoModel.removeEventListener('ended', _onNativePlaybackEnded); + videoModel.removeEventListener('error', _onPlaybackError); + videoModel.removeEventListener('loadeddata', _onPlaybackLoadedData); + videoModel.removeEventListener('loadedmetadata', _onPlaybackMetaDataLoaded); + videoModel.removeEventListener('pause', _onPlaybackPaused); videoModel.removeEventListener('play', _onPlaybackStart); - videoModel.removeEventListener('waiting', _onPlaybackWaiting); videoModel.removeEventListener('playing', _onPlaybackPlaying); - videoModel.removeEventListener('pause', _onPlaybackPaused); - videoModel.removeEventListener('error', _onPlaybackError); - videoModel.removeEventListener('seeking', _onPlaybackSeeking); - videoModel.removeEventListener('seeked', _onPlaybackSeeked); - videoModel.removeEventListener('timeupdate', _onPlaybackTimeUpdated); videoModel.removeEventListener('progress', _onPlaybackProgress); videoModel.removeEventListener('ratechange', _onPlaybackRateChanged); - videoModel.removeEventListener('loadedmetadata', _onPlaybackMetaDataLoaded); - videoModel.removeEventListener('loadeddata', _onPlaybackLoadedData); + videoModel.removeEventListener('seeked', _onPlaybackSeeked); + videoModel.removeEventListener('seeking', _onPlaybackSeeking); videoModel.removeEventListener('stalled', onPlaybackStalled); - videoModel.removeEventListener('ended', _onNativePlaybackEnded); + videoModel.removeEventListener('timeupdate', _onPlaybackTimeUpdated); videoModel.removeEventListener('volumechange', _onVolumeChanged); + videoModel.removeEventListener('waiting', _onPlaybackWaiting); } instance = { diff --git a/src/streaming/models/VideoModel.js b/src/streaming/models/VideoModel.js index 6c8fb58e24..268b2c82b3 100644 --- a/src/streaming/models/VideoModel.js +++ b/src/streaming/models/VideoModel.js @@ -46,17 +46,19 @@ const READY_STATES_TO_EVENT_NAMES = new Map([ function VideoModel() { - let instance, - logger, - settings, - element, + let TTMLRenderingDiv, _currentTime, - setCurrentTimeReadyStateFunction, - resumeReadyStateFunction, - TTMLRenderingDiv, - vttRenderingDiv, + element, + instance, + logger, previousPlaybackRate, - timeout; + resizeObserver, + resumeReadyStateFunction, + setCurrentTimeReadyStateFunction, + settings, + timeout, + vttRenderingDiv; + const VIDEO_MODEL_WRONG_ELEMENT_TYPE = 'element is not video or audio DOM type!'; @@ -68,6 +70,17 @@ function VideoModel() { logger = Debug(context).getInstance().getLogger(instance); settings = Settings(context).getInstance(); _currentTime = NaN; + _createResizeObserver(); + } + + function _createResizeObserver() { + try { + resizeObserver = new ResizeObserver(() => { + eventBus.trigger(Events.VIDEO_ELEMENT_RESIZED); + }); + } catch (e) { + + } } function initialize() { @@ -78,6 +91,19 @@ function VideoModel() { clearTimeout(timeout); eventBus.off(Events.PLAYBACK_PLAYING, onPlaying, this); stalledStreams.length = 0; + _disposeResizeObserver(); + } + + function _disposeResizeObserver() { + try { + if (resizeObserver && element) { + resizeObserver.unobserve(element); + resizeObserver.disconnect(); + resizeObserver = null; + } + } catch (e) { + + } } function setConfig(config) { @@ -185,11 +211,23 @@ function VideoModel() { //add check of value type if (value === null || value === undefined || (value && (/^(VIDEO|AUDIO)$/i).test(value.nodeName))) { element = value; + _registerResizeObserver(element); } else { throw VIDEO_MODEL_WRONG_ELEMENT_TYPE; } } + function _registerResizeObserver(element) { + try { + if (!resizeObserver || !element) { + return; + } + resizeObserver.observe(element); + } catch (e) { + + } + } + function setSource(source) { if (element) { if (source) { diff --git a/src/streaming/rules/abr/BolaRule.js b/src/streaming/rules/abr/BolaRule.js index a9afd3a93b..8a2844b4e5 100644 --- a/src/streaming/rules/abr/BolaRule.js +++ b/src/streaming/rules/abr/BolaRule.js @@ -85,6 +85,7 @@ function BolaRule(config) { eventBus.on(Events.MEDIA_FRAGMENT_LOADED, _onMediaFragmentLoaded, instance); eventBus.on(Events.SETTING_UPDATED_MAX_BITRATE, _onMinMaxBitrateUpdated, instance); eventBus.on(Events.SETTING_UPDATED_MIN_BITRATE, _onMinMaxBitrateUpdated, instance); + eventBus.on(Events.VIDEO_ELEMENT_RESIZED, _onVideoElementResized, instance); } /** @@ -393,7 +394,17 @@ function BolaRule(config) { * @private */ function _onMinMaxBitrateUpdated() { - resetInitialSettings() + resetInitialSettings(); + } + + /** + * We need to reset to initial settings because the number of available Representations might have changed + * @private + */ + function _onVideoElementResized() { + if (settings.get().streaming.abr.limitBitrateByPortal) { + resetInitialSettings(); + } } @@ -631,6 +642,7 @@ function BolaRule(config) { eventBus.off(Events.MEDIA_FRAGMENT_LOADED, _onMediaFragmentLoaded, instance); eventBus.off(Events.SETTING_UPDATED_MAX_BITRATE, _onMinMaxBitrateUpdated, instance); eventBus.off(Events.SETTING_UPDATED_MIN_BITRATE, _onMinMaxBitrateUpdated, instance); + eventBus.off(Events.VIDEO_ELEMENT_RESIZED, _onVideoElementResized, instance); } instance = { diff --git a/test/unit/mocks/AbrControllerMock.js b/test/unit/mocks/AbrControllerMock.js index 4f10a3324c..01f6530818 100644 --- a/test/unit/mocks/AbrControllerMock.js +++ b/test/unit/mocks/AbrControllerMock.js @@ -4,7 +4,6 @@ function AbrControllerMock () { this.qualityDict = {}; this.elementWidth = undefined; this.elementHeight = undefined; - this.windowResizeEventCalled = false; this.currentStreamId = undefined; this.topBitrateInfo = null; let self = this; @@ -65,14 +64,6 @@ function AbrControllerMock () { this.qualityDict[id][type] = value; }; - this.setWindowResizeEventCalled = function (value) { - this.windowResizeEventCalled = value; - }; - - this.getWindowResizeEventCalled = function () { - return this.windowResizeEventCalled; - }; - this.setElementSize = function () { this.elementWidth = 10; this.elementHeight = 10;