mirror of
				https://github.com/videojs/video.js.git
				synced 2025-10-31 00:08:01 +02:00 
			
		
		
		
	feat: Add methods to add and remove <source> elements (#8886)
## Description It is useful to have methods for appending and removing `<source>` elements to the `<video>` element, as they are sometimes required to enable certain playback features, for example, using [Airplay with MSE](https://webkit.org/blog/15036/how-to-use-media-source-extensions-with-airplay). ## Specific Changes proposed Add new methods-- `addSourceElement()` and `removeSourceElement()` to the player and tech. The former will take a source object and create and append a new `<source>` element to the `<video>` element, and the latter will take a source url and remove any `<source>` element with a matching `src`. ## Requirements Checklist - [ ] Feature implemented / Bug fixed - [ ] If necessary, more likely in a feature request than a bug fix - [ ] Change has been verified in an actual browser (Chrome, Firefox, IE) - [ ] Unit Tests updated or fixed - [ ] Docs/guides updated - [ ] Example created ([starter template on JSBin](https://codepen.io/gkatsev/pen/GwZegv?editors=1000#0)) - [ ] Has no DOM changes which impact accessiblilty or trigger warnings (e.g. Chrome issues tab) - [ ] Has no changes to JSDoc which cause `npm run docs:api` to error - [ ] Reviewed by Two Core Contributors
This commit is contained in:
		| @@ -3710,6 +3710,43 @@ class Player extends Component { | |||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Add a <source> element to the <video> element. | ||||||
|  |    * | ||||||
|  |    * @param {string} srcUrl | ||||||
|  |    *        The URL of the video source. | ||||||
|  |    * | ||||||
|  |    * @param {string} [mimeType] | ||||||
|  |    *        The MIME type of the video source. Optional but recommended. | ||||||
|  |    * | ||||||
|  |    * @return {boolean} | ||||||
|  |    *         Returns true if the source element was successfully added, false otherwise. | ||||||
|  |    */ | ||||||
|  |   addSourceElement(srcUrl, mimeType) { | ||||||
|  |     if (!this.tech_) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return this.tech_.addSourceElement(srcUrl, mimeType); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Remove a <source> element from the <video> element by its URL. | ||||||
|  |    * | ||||||
|  |    * @param {string} srcUrl | ||||||
|  |    *        The URL of the source to remove. | ||||||
|  |    * | ||||||
|  |    * @return {boolean} | ||||||
|  |    *         Returns true if the source element was successfully removed, false otherwise. | ||||||
|  |    */ | ||||||
|  |   removeSourceElement(srcUrl) { | ||||||
|  |     if (!this.tech_) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return this.tech_.removeSourceElement(srcUrl); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Begin loading the src data. |    * Begin loading the src data. | ||||||
|    */ |    */ | ||||||
|   | |||||||
| @@ -783,6 +783,67 @@ class Html5 extends Tech { | |||||||
|     this.setSrc(src); |     this.setSrc(src); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Add a <source> element to the <video> element. | ||||||
|  |    * | ||||||
|  |    * @param {string} srcUrl | ||||||
|  |    *        The URL of the video source. | ||||||
|  |    * | ||||||
|  |    * @param {string} [mimeType] | ||||||
|  |    *        The MIME type of the video source. Optional but recommended. | ||||||
|  |    * | ||||||
|  |    * @return {boolean} | ||||||
|  |    *         Returns true if the source element was successfully added, false otherwise. | ||||||
|  |    */ | ||||||
|  |   addSourceElement(srcUrl, mimeType) { | ||||||
|  |     if (!srcUrl) { | ||||||
|  |       log.error('Invalid source URL.'); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const sourceAttributes = { src: srcUrl }; | ||||||
|  |  | ||||||
|  |     if (mimeType) { | ||||||
|  |       sourceAttributes.type = mimeType; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const sourceElement = Dom.createEl('source', {}, sourceAttributes); | ||||||
|  |  | ||||||
|  |     this.el_.appendChild(sourceElement); | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Remove a <source> element from the <video> element by its URL. | ||||||
|  |    * | ||||||
|  |    * @param {string} srcUrl | ||||||
|  |    *        The URL of the source to remove. | ||||||
|  |    * | ||||||
|  |    * @return {boolean} | ||||||
|  |    *         Returns true if the source element was successfully removed, false otherwise. | ||||||
|  |    */ | ||||||
|  |   removeSourceElement(srcUrl) { | ||||||
|  |     if (!srcUrl) { | ||||||
|  |       log.error('Source URL is required to remove the source element.'); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const sourceElements = this.el_.querySelectorAll('source'); | ||||||
|  |  | ||||||
|  |     for (const sourceElement of sourceElements) { | ||||||
|  |       if (sourceElement.src === srcUrl) { | ||||||
|  |         this.el_.removeChild(sourceElement); | ||||||
|  |  | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     log.warn(`No matching source element found with src: ${srcUrl}`); | ||||||
|  |  | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Reset the tech by removing all sources and then calling |    * Reset the tech by removing all sources and then calling | ||||||
|    * {@link Html5.resetMediaElement}. |    * {@link Html5.resetMediaElement}. | ||||||
|   | |||||||
| @@ -3611,3 +3611,57 @@ QUnit.test('smooth seeking set to true should update the display time components | |||||||
|   player.dispose(); |   player.dispose(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | QUnit.test('addSourceElement calls tech method with correct args', function(assert) { | ||||||
|  |   const player = TestHelpers.makePlayer(); | ||||||
|  |   const addSourceElementSpy = sinon.spy(player.tech_, 'addSourceElement'); | ||||||
|  |   const srcUrl = 'http://example.com/video.mp4'; | ||||||
|  |   const mimeType = 'video/mp4'; | ||||||
|  |  | ||||||
|  |   player.addSourceElement(srcUrl, mimeType); | ||||||
|  |  | ||||||
|  |   assert.ok(addSourceElementSpy.calledOnce, 'addSourceElement method called'); | ||||||
|  |   assert.ok(addSourceElementSpy.calledWith(srcUrl, mimeType), 'addSourceElement called with correct arguments'); | ||||||
|  |  | ||||||
|  |   addSourceElementSpy.restore(); | ||||||
|  |   player.dispose(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | QUnit.test('addSourceElement returns false if no tech', function(assert) { | ||||||
|  |   const player = TestHelpers.makePlayer(); | ||||||
|  |   const srcUrl = 'http://example.com/video.mp4'; | ||||||
|  |   const mimeType = 'video/mp4'; | ||||||
|  |  | ||||||
|  |   player.tech_ = undefined; | ||||||
|  |  | ||||||
|  |   const added = player.addSourceElement(srcUrl, mimeType); | ||||||
|  |  | ||||||
|  |   assert.notOk(added, 'Returned false'); | ||||||
|  |   player.dispose(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | QUnit.test('removeSourceElement calls tech method with correct args', function(assert) { | ||||||
|  |   const player = TestHelpers.makePlayer(); | ||||||
|  |   const removeSourceElementSpy = sinon.spy(player.tech_, 'removeSourceElement'); | ||||||
|  |   const srcUrl = 'http://example.com/video.mp4'; | ||||||
|  |  | ||||||
|  |   player.removeSourceElement(srcUrl); | ||||||
|  |  | ||||||
|  |   assert.ok(removeSourceElementSpy.calledOnce, 'removeSourceElement method called'); | ||||||
|  |   assert.ok(removeSourceElementSpy.calledWith(srcUrl), 'removeSourceElement called with correct arguments'); | ||||||
|  |  | ||||||
|  |   removeSourceElementSpy.restore(); | ||||||
|  |   player.dispose(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | QUnit.test('removeSourceElement returns false if no tech', function(assert) { | ||||||
|  |   const player = TestHelpers.makePlayer(); | ||||||
|  |   const srcUrl = 'http://example.com/video.mp4'; | ||||||
|  |   const mimeType = 'video/mp4'; | ||||||
|  |  | ||||||
|  |   player.tech_ = undefined; | ||||||
|  |  | ||||||
|  |   const removed = player.removeSourceElement(srcUrl, mimeType); | ||||||
|  |  | ||||||
|  |   assert.notOk(removed, 'Returned false'); | ||||||
|  |   player.dispose(); | ||||||
|  | }); | ||||||
|   | |||||||
| @@ -905,3 +905,79 @@ QUnit.test('supportsFullScreen is always with `webkitEnterFullScreen`', function | |||||||
|  |  | ||||||
|   tech.el_ = oldEl; |   tech.el_ = oldEl; | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | QUnit.test('addSourceElement adds a valid source element with srcUrl and mimeType', function(assert) { | ||||||
|  |   const videoEl = document.createElement('video'); | ||||||
|  |  | ||||||
|  |   tech.el_ = videoEl; | ||||||
|  |  | ||||||
|  |   const srcUrl = 'http://example.com/video.mp4'; | ||||||
|  |   const mimeType = 'video/mp4'; | ||||||
|  |  | ||||||
|  |   const added = tech.addSourceElement(srcUrl, mimeType); | ||||||
|  |   const sourceElement = videoEl.querySelector('source'); | ||||||
|  |  | ||||||
|  |   assert.ok(added, 'Returned true'); | ||||||
|  |   assert.ok(sourceElement, 'A source element was added'); | ||||||
|  |   assert.equal(sourceElement.src, srcUrl, 'Source element has correct src'); | ||||||
|  |   assert.equal(sourceElement.type, mimeType, 'Source element has correct type'); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | QUnit.test('addSourceElement adds a valid source element without a mimeType', function(assert) { | ||||||
|  |   const videoEl = document.createElement('video'); | ||||||
|  |  | ||||||
|  |   tech.el_ = videoEl; | ||||||
|  |  | ||||||
|  |   const srcUrl = 'http://example.com/video2.mp4'; | ||||||
|  |  | ||||||
|  |   const added = tech.addSourceElement(srcUrl); | ||||||
|  |   const sourceElement = videoEl.querySelector('source'); | ||||||
|  |  | ||||||
|  |   assert.ok(added, 'Returned true'); | ||||||
|  |   assert.ok(sourceElement, 'A source element was added even without a type'); | ||||||
|  |   assert.equal(sourceElement.src, srcUrl, 'Source element has correct src'); | ||||||
|  |   assert.notOk(sourceElement.type, 'Source element does not have a type attribute'); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | QUnit.test('addSourceElement does not add a source element for invalid source URL', function(assert) { | ||||||
|  |   const videoEl = document.createElement('video'); | ||||||
|  |  | ||||||
|  |   tech.el_ = videoEl; | ||||||
|  |  | ||||||
|  |   const added = tech.addSourceElement(''); | ||||||
|  |   const sourceElement = videoEl.querySelector('source'); | ||||||
|  |  | ||||||
|  |   assert.notOk(added, 'Returned false'); | ||||||
|  |   assert.notOk(sourceElement, 'No source element was added for missing src'); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | QUnit.test('removeSourceElement removes a source element by its URL', function(assert) { | ||||||
|  |   const videoEl = document.createElement('video'); | ||||||
|  |  | ||||||
|  |   tech.el_ = videoEl; | ||||||
|  |  | ||||||
|  |   tech.addSourceElement('http://example.com/video1.mp4'); | ||||||
|  |   tech.addSourceElement('http://example.com/video2.mp4'); | ||||||
|  |  | ||||||
|  |   let sourceElement = videoEl.querySelector('source[src="http://example.com/video1.mp4"]'); | ||||||
|  |  | ||||||
|  |   assert.ok(sourceElement, 'Source element for video1.mp4 was added'); | ||||||
|  |  | ||||||
|  |   const removed = tech.removeSourceElement('http://example.com/video1.mp4'); | ||||||
|  |  | ||||||
|  |   assert.ok(removed, 'Source element was successfully removed'); | ||||||
|  |   sourceElement = videoEl.querySelector('source[src="http://example.com/video1.mp4"]'); | ||||||
|  |   assert.notOk(sourceElement, 'Source element for video1.mp4 was removed'); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | QUnit.test('removeSourceElement does not remove a source element if URL does not match', function(assert) { | ||||||
|  |   const videoEl = document.createElement('video'); | ||||||
|  |  | ||||||
|  |   tech.el_ = videoEl; | ||||||
|  |  | ||||||
|  |   tech.addSourceElement('http://example.com/video.mp4'); | ||||||
|  |  | ||||||
|  |   const removed = tech.removeSourceElement('http://example.com/invalid.mp4'); | ||||||
|  |  | ||||||
|  |   assert.notOk(removed, 'No source element was removed for non-matching URL'); | ||||||
|  | }); | ||||||
|   | |||||||
| @@ -111,6 +111,8 @@ class TechFaker extends Tech { | |||||||
|     } |     } | ||||||
|     return 'movie.mp4'; |     return 'movie.mp4'; | ||||||
|   } |   } | ||||||
|  |   addSourceElement() {} | ||||||
|  |   removeSourceElement() {} | ||||||
|   load() { |   load() { | ||||||
|   } |   } | ||||||
|   currentSrc() { |   currentSrc() { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user