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; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 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. | ||||
|    */ | ||||
|   | ||||
| @@ -783,6 +783,67 @@ class Html5 extends Tech { | ||||
|     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 | ||||
|    * {@link Html5.resetMediaElement}. | ||||
|   | ||||
| @@ -3611,3 +3611,57 @@ QUnit.test('smooth seeking set to true should update the display time components | ||||
|   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; | ||||
| }); | ||||
|  | ||||
| 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'; | ||||
|   } | ||||
|   addSourceElement() {} | ||||
|   removeSourceElement() {} | ||||
|   load() { | ||||
|   } | ||||
|   currentSrc() { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user