Skip to content

Commit f9662d0

Browse files
Reset mediaElement and mediaPlayer on call to reset when configured with resetMSEPlayer (#353)
* added first pass and resetting mediaPlayer * added reset tests * add configuration for fully resetting MSE player * ensure mediaPlayer isn't destroyed if it doesn't exist * refactor to clean up --------- Co-authored-by: Matt Stephenson <matt.stephenson@bbc.co.uk>
1 parent ff7c3da commit f9662d0

File tree

2 files changed

+146
-33
lines changed

2 files changed

+146
-33
lines changed

src/playbackstrategy/msestrategy.js

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,46 @@ function MSEStrategy(mediaSources, windowType, mediaKind, playbackElement, isUHD
633633
)
634634
}
635635

636+
function cleanUpMediaPlayer() {
637+
if (mediaPlayer) {
638+
mediaPlayer.destroy()
639+
640+
mediaPlayer.off(DashJSEvents.ERROR, onError)
641+
mediaPlayer.off(DashJSEvents.MANIFEST_LOADED, onManifestLoaded)
642+
mediaPlayer.off(DashJSEvents.MANIFEST_VALIDITY_CHANGED, onManifestValidityChange)
643+
mediaPlayer.off(DashJSEvents.STREAM_INITIALIZED, onStreamInitialised)
644+
mediaPlayer.off(DashJSEvents.QUALITY_CHANGE_RENDERED, onQualityChangeRendered)
645+
mediaPlayer.off(DashJSEvents.METRIC_ADDED, onMetricAdded)
646+
mediaPlayer.off(DashJSEvents.BASE_URL_SELECTED, onBaseUrlSelected)
647+
mediaPlayer.off(DashJSEvents.LOG, onDebugLog)
648+
mediaPlayer.off(DashJSEvents.SERVICE_LOCATION_AVAILABLE, onServiceLocationAvailable)
649+
mediaPlayer.off(DashJSEvents.URL_RESOLUTION_FAILED, onURLResolutionFailed)
650+
mediaPlayer.off(DashJSEvents.GAP_JUMP, onGapJump)
651+
mediaPlayer.off(DashJSEvents.GAP_JUMP_TO_END, onGapJump)
652+
mediaPlayer.off(DashJSEvents.QUOTA_EXCEEDED, onQuotaExceeded)
653+
654+
mediaPlayer = undefined
655+
}
656+
657+
if (mediaElement) {
658+
mediaElement.removeEventListener("timeupdate", onTimeUpdate)
659+
mediaElement.removeEventListener("loadedmetadata", onLoadedMetaData)
660+
mediaElement.removeEventListener("loadeddata", onLoadedData)
661+
mediaElement.removeEventListener("play", onPlay)
662+
mediaElement.removeEventListener("playing", onPlaying)
663+
mediaElement.removeEventListener("pause", onPaused)
664+
mediaElement.removeEventListener("waiting", onWaiting)
665+
mediaElement.removeEventListener("seeking", onSeeking)
666+
mediaElement.removeEventListener("seeked", onSeeked)
667+
mediaElement.removeEventListener("ended", onEnded)
668+
mediaElement.removeEventListener("ratechange", onRateChange)
669+
670+
DOMHelpers.safeRemoveElement(mediaElement)
671+
672+
mediaElement = undefined
673+
}
674+
}
675+
636676
return {
637677
transitions: {
638678
canBePaused: () => true,
@@ -652,38 +692,9 @@ function MSEStrategy(mediaSources, windowType, mediaKind, playbackElement, isUHD
652692
getDuration,
653693
getPlayerElement: () => mediaElement,
654694
tearDown: () => {
655-
mediaPlayer.reset()
656-
657-
mediaElement.removeEventListener("timeupdate", onTimeUpdate)
658-
mediaElement.removeEventListener("loadedmetadata", onLoadedMetaData)
659-
mediaElement.removeEventListener("loadeddata", onLoadedData)
660-
mediaElement.removeEventListener("play", onPlay)
661-
mediaElement.removeEventListener("playing", onPlaying)
662-
mediaElement.removeEventListener("pause", onPaused)
663-
mediaElement.removeEventListener("waiting", onWaiting)
664-
mediaElement.removeEventListener("seeking", onSeeking)
665-
mediaElement.removeEventListener("seeked", onSeeked)
666-
mediaElement.removeEventListener("ended", onEnded)
667-
mediaElement.removeEventListener("ratechange", onRateChange)
668-
mediaPlayer.off(DashJSEvents.ERROR, onError)
669-
mediaPlayer.off(DashJSEvents.MANIFEST_LOADED, onManifestLoaded)
670-
mediaPlayer.off(DashJSEvents.MANIFEST_VALIDITY_CHANGED, onManifestValidityChange)
671-
mediaPlayer.off(DashJSEvents.STREAM_INITIALIZED, onStreamInitialised)
672-
mediaPlayer.off(DashJSEvents.QUALITY_CHANGE_RENDERED, onQualityChangeRendered)
673-
mediaPlayer.off(DashJSEvents.METRIC_ADDED, onMetricAdded)
674-
mediaPlayer.off(DashJSEvents.BASE_URL_SELECTED, onBaseUrlSelected)
675-
mediaPlayer.off(DashJSEvents.LOG, onDebugLog)
676-
mediaPlayer.off(DashJSEvents.SERVICE_LOCATION_AVAILABLE, onServiceLocationAvailable)
677-
mediaPlayer.off(DashJSEvents.URL_RESOLUTION_FAILED, onURLResolutionFailed)
678-
mediaPlayer.off(DashJSEvents.GAP_JUMP, onGapJump)
679-
mediaPlayer.off(DashJSEvents.GAP_JUMP_TO_END, onGapJump)
680-
mediaPlayer.off(DashJSEvents.QUOTA_EXCEEDED, onQuotaExceeded)
681-
682-
DOMHelpers.safeRemoveElement(mediaElement)
695+
cleanUpMediaPlayer()
683696

684697
lastError = undefined
685-
mediaPlayer = undefined
686-
mediaElement = undefined
687698
eventCallbacks = []
688699
errorCallback = undefined
689700
timeUpdateCallback = undefined
@@ -701,7 +712,11 @@ function MSEStrategy(mediaSources, windowType, mediaKind, playbackElement, isUHD
701712
},
702713
}
703714
},
704-
reset: () => {},
715+
reset: () => {
716+
if (window.bigscreenPlayer.overrides && window.bigscreenPlayer.overrides.resetMSEPlayer) {
717+
cleanUpMediaPlayer()
718+
}
719+
},
705720
isEnded: () => isEnded,
706721
isPaused,
707722
pause: (opts = {}) => {

src/playbackstrategy/msestrategy.test.js

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const mockDashInstance = {
2222
duration: jest.fn(),
2323
attachSource: jest.fn(),
2424
reset: jest.fn(),
25+
destroy: jest.fn(),
2526
isPaused: jest.fn(),
2627
pause: jest.fn(),
2728
play: jest.fn(),
@@ -654,14 +655,106 @@ describe("Media Source Extensions Playback Strategy", () => {
654655
})
655656
})
656657

658+
describe("reset()", () => {
659+
describe("when resetMSEPlayer is configured as true", () => {
660+
beforeEach(() => {
661+
window.bigscreenPlayer.overrides = {
662+
resetMSEPlayer: true,
663+
}
664+
})
665+
666+
it("should destroy the player and listeners", () => {
667+
setUpMSE()
668+
mseStrategy.load(null, 0)
669+
670+
expect(playbackElement.childElementCount).toBe(1)
671+
672+
mseStrategy.reset()
673+
674+
expect(mockDashInstance.destroy).toHaveBeenCalledWith()
675+
676+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("timeupdate", expect.any(Function))
677+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("loadedmetadata", expect.any(Function))
678+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("loadeddata", expect.any(Function))
679+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("play", expect.any(Function))
680+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("playing", expect.any(Function))
681+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("pause", expect.any(Function))
682+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("waiting", expect.any(Function))
683+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("seeking", expect.any(Function))
684+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("seeked", expect.any(Function))
685+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("ended", expect.any(Function))
686+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("ratechange", expect.any(Function))
687+
688+
expect(playbackElement.childElementCount).toBe(0)
689+
})
690+
691+
it("should setup player and element on a load after a reset", () => {
692+
setUpMSE()
693+
mseStrategy.load(null, 0)
694+
695+
mseStrategy.reset()
696+
697+
jest.clearAllMocks()
698+
699+
jest.spyOn(document, "createElement").mockImplementationOnce((elementType) => {
700+
if (["audio", "video"].includes(elementType)) {
701+
mediaElement = mockMediaElement(elementType)
702+
return mediaElement
703+
}
704+
705+
return document.createElement(elementType)
706+
})
707+
708+
mseStrategy.load(null, 0)
709+
710+
expect(mockDashInstance.initialize).toHaveBeenCalledTimes(1)
711+
expect(mockDashInstance.initialize).toHaveBeenCalledWith(mediaElement, null, true)
712+
expect(mockDashInstance.attachSource).toHaveBeenCalledWith(cdnArray[0].url)
713+
714+
expect(playbackElement.childElementCount).toBe(1)
715+
expect(playbackElement.firstChild).toBeInstanceOf(HTMLVideoElement)
716+
expect(playbackElement.firstChild).toBe(mediaElement)
717+
expect(isMockedElement(playbackElement.firstChild)).toBe(true)
718+
})
719+
})
720+
describe("when resetMSEPlayer is configured as false", () => {
721+
beforeEach(() => {
722+
window.bigscreenPlayer.overrides = {
723+
resetMSEPlayer: false,
724+
}
725+
})
726+
727+
it("should not destroy the player or listeners", () => {
728+
setUpMSE()
729+
mseStrategy.load(null, 0)
730+
mseStrategy.reset()
731+
732+
expect(mockDashInstance.destroy).not.toHaveBeenCalledWith()
733+
expect(playbackElement.childElementCount).toBe(1)
734+
735+
expect(mediaElement.removeEventListener).not.toHaveBeenCalledWith("timeupdate", expect.any(Function))
736+
expect(mediaElement.removeEventListener).not.toHaveBeenCalledWith("loadedmetadata", expect.any(Function))
737+
expect(mediaElement.removeEventListener).not.toHaveBeenCalledWith("loadeddata", expect.any(Function))
738+
expect(mediaElement.removeEventListener).not.toHaveBeenCalledWith("play", expect.any(Function))
739+
expect(mediaElement.removeEventListener).not.toHaveBeenCalledWith("playing", expect.any(Function))
740+
expect(mediaElement.removeEventListener).not.toHaveBeenCalledWith("pause", expect.any(Function))
741+
expect(mediaElement.removeEventListener).not.toHaveBeenCalledWith("waiting", expect.any(Function))
742+
expect(mediaElement.removeEventListener).not.toHaveBeenCalledWith("seeking", expect.any(Function))
743+
expect(mediaElement.removeEventListener).not.toHaveBeenCalledWith("seeked", expect.any(Function))
744+
expect(mediaElement.removeEventListener).not.toHaveBeenCalledWith("ended", expect.any(Function))
745+
expect(mediaElement.removeEventListener).not.toHaveBeenCalledWith("ratechange", expect.any(Function))
746+
})
747+
})
748+
})
749+
657750
describe("tearDown()", () => {
658-
it("should reset the MediaPlayer", () => {
751+
it("should destroy the MediaPlayer", () => {
659752
setUpMSE()
660753
mseStrategy.load(null, 0)
661754

662755
mseStrategy.tearDown()
663756

664-
expect(mockDashInstance.reset).toHaveBeenCalledWith()
757+
expect(mockDashInstance.destroy).toHaveBeenCalledWith()
665758
})
666759

667760
it("should tear down bindings to MediaPlayer Events correctly", () => {
@@ -671,12 +764,17 @@ describe("Media Source Extensions Playback Strategy", () => {
671764
mseStrategy.tearDown()
672765

673766
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("timeupdate", expect.any(Function))
767+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("loadedmetadata", expect.any(Function))
768+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("loadeddata", expect.any(Function))
769+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("play", expect.any(Function))
674770
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("playing", expect.any(Function))
675771
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("pause", expect.any(Function))
676772
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("waiting", expect.any(Function))
677773
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("seeking", expect.any(Function))
678774
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("seeked", expect.any(Function))
679775
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("ended", expect.any(Function))
776+
expect(mediaElement.removeEventListener).toHaveBeenCalledWith("ratechange", expect.any(Function))
777+
680778
expect(mockDashInstance.off).toHaveBeenCalledWith(dashjsMediaPlayerEvents.ERROR, expect.any(Function))
681779
expect(mockDashInstance.off).toHaveBeenCalledWith(
682780
dashjsMediaPlayerEvents.QUALITY_CHANGE_RENDERED,

0 commit comments

Comments
 (0)