1
0
mirror of https://github.com/videojs/video.js.git synced 2025-01-02 06:32:07 +02:00

Merge branch 'main' into type-updates

This commit is contained in:
mister-ben 2024-10-23 15:48:56 +02:00 committed by GitHub
commit 651dc44513
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 305 additions and 105 deletions

View File

@ -1,3 +1,33 @@
<a name="8.19.1"></a>
## [8.19.1](https://github.com/videojs/video.js/compare/v8.19.0...v8.19.1) (2024-10-10)
### Chores
* **package:** update http-streaming to v3.15.0 ([#8889](https://github.com/videojs/video.js/issues/8889)) ([84f4905](https://github.com/videojs/video.js/commit/84f4905))
* update mpd-parser to v1.3.1 ([#8888](https://github.com/videojs/video.js/issues/8888)) ([7c17d75](https://github.com/videojs/video.js/commit/7c17d75))
<a name="8.19.0"></a>
# [8.19.0](https://github.com/videojs/video.js/compare/v8.18.0...v8.19.0) (2024-10-09)
### Features
* Add methods to add and remove <source> elements ([#8886](https://github.com/videojs/video.js/issues/8886)) ([eddda97](https://github.com/videojs/video.js/commit/eddda97)), closes [1000#0](https://github.com/1000/issues/0)
### Bug Fixes
* Don't request fullscreen from document PIP window ([#8881](https://github.com/videojs/video.js/issues/8881)) ([077077b](https://github.com/videojs/video.js/commit/077077b)), closes [#8877](https://github.com/videojs/video.js/issues/8877)
### Chores
* **package:** Update to VHS v3.14.2 ([#8869](https://github.com/videojs/video.js/issues/8869)) ([89eb454](https://github.com/videojs/video.js/commit/89eb454))
<a name="8.18.1"></a>
## [8.18.1](https://github.com/videojs/video.js/compare/v8.18.0...v8.18.1) (2024-09-17)
### Chores
* **package:** Update to VHS v3.14.2 ([#8869](https://github.com/videojs/video.js/issues/8869)) ([89eb454](https://github.com/videojs/video.js/commit/89eb454))
<a name="8.18.0"></a>
# [8.18.0](https://github.com/videojs/video.js/compare/v8.17.4...v8.18.0) (2024-09-10)

View File

@ -22,8 +22,8 @@ Video.js was started in the middle of 2010 and is now used on over ~~50,000~~ ~~
Thanks to the awesome folks over at [Fastly][fastly], there's a free, CDN hosted version of Video.js that anyone can use. Add these tags to your document's `<head>`:
```html
<link href="//vjs.zencdn.net/8.18.0/video-js.min.css" rel="stylesheet">
<script src="//vjs.zencdn.net/8.18.0/video.min.js"></script>
<link href="//vjs.zencdn.net/8.19.1/video-js.min.css" rel="stylesheet">
<script src="//vjs.zencdn.net/8.19.1/video.min.js"></script>
```
Alternatively, you can include Video.js by getting it from [npm](https://videojs.com/getting-started/#install-via-npm), downloading it from [GitHub releases](https://github.com/videojs/video.js/releases) or by including it via [unpkg](https://unpkg.com) or another JavaScript CDN, like CDNjs.
@ -34,12 +34,12 @@ Alternatively, you can include Video.js by getting it from [npm](https://videojs
<script src="https://unpkg.com/video.js/dist/video.min.js"></script>
<!-- unpkg : use a specific version of Video.js (change the version numbers as necessary) -->
<link href="https://unpkg.com/video.js@8.18.0/dist/video-js.min.css" rel="stylesheet">
<script src="https://unpkg.com/video.js@8.18.0/dist/video.min.js"></script>
<link href="https://unpkg.com/video.js@8.19.1/dist/video-js.min.css" rel="stylesheet">
<script src="https://unpkg.com/video.js@8.19.1/dist/video.min.js"></script>
<!-- cdnjs : use a specific version of Video.js (change the version numbers as necessary) -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/video.js/8.18.0/video-js.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/video.js/8.18.0/video.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/video.js/8.19.1/video-js.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/video.js/8.19.1/video.min.js"></script>
```
Next, using Video.js is as simple as creating a `<video>` element, but with an additional `data-setup` attribute. At a minimum, this attribute must have a value of `'{}'`, but it can include any Video.js [options][options] - just make sure it contains valid JSON!

109
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "video.js",
"version": "8.18.0",
"version": "8.19.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -1791,16 +1791,16 @@
}
},
"@videojs/http-streaming": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.14.1.tgz",
"integrity": "sha512-VQ0msr+xTkA8BsDBMG9fqXMIvkPYXguVrOOPcrw+sk8y6xgrJjdfzie2h25uUMg/JUbEQqp1CbsK6IuIMqRFoA==",
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.15.0.tgz",
"integrity": "sha512-6rjaqEa87gVFqDFsHaLKXGrDqL3NhNZRNi6wkMw+uyt1lrLD2OFY0SfRQRNl7Vmmx0pt5FRJoRJYlnKsowyElA==",
"requires": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^4.1.1",
"aes-decrypter": "^4.0.2",
"global": "^4.4.0",
"m3u8-parser": "^7.2.0",
"mpd-parser": "^1.3.0",
"mpd-parser": "^1.3.1",
"mux.js": "7.0.3",
"video.js": "^7 || ^8"
}
@ -7282,11 +7282,6 @@
"integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==",
"dev": true
},
"individual": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz",
"integrity": "sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g=="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -9744,9 +9739,9 @@
}
},
"mpd-parser": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.0.tgz",
"integrity": "sha512-WgeIwxAqkmb9uTn4ClicXpEQYCEduDqRKfmUdp4X8vmghKfBNXZLYpREn9eqrDx/Tf5LhzRcJLSpi4ohfV742Q==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.1.tgz",
"integrity": "sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==",
"requires": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^4.0.0",
@ -12486,14 +12481,6 @@
"queue-microtask": "^1.2.2"
}
},
"rust-result": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz",
"integrity": "sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==",
"requires": {
"individual": "^2.0.0"
}
},
"rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
@ -12529,14 +12516,6 @@
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true
},
"safe-json-parse": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz",
"integrity": "sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==",
"requires": {
"rust-result": "^1.0.0"
}
},
"safe-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
@ -14846,11 +14825,6 @@
"prepend-http": "^2.0.0"
}
},
"url-toolkit": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz",
"integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg=="
},
"urljoin": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/urljoin/-/urljoin-0.1.5.tgz",
@ -15076,75 +15050,22 @@
"dev": true
},
"video.js": {
"version": "8.17.3",
"resolved": "https://registry.npmjs.org/video.js/-/video.js-8.17.3.tgz",
"integrity": "sha512-zhhmE0LNxJRA603/48oYzF7GYdT+rQRscvcsouYxFE71aKhalHLBP6S9/XjixnyjcrYgwIx8OQo6eSjcbbAW0Q==",
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/video.js/-/video.js-8.18.1.tgz",
"integrity": "sha512-oQ4M/HD2fFgEPHfmVMWxGykRFIpOmVhK0XZ4PSsPTgN2jH6E6+92f/RI2mDXDb0yu+Fxv9fxMUm0M7Z2K3Zo9w==",
"requires": {
"@babel/runtime": "^7.12.5",
"@videojs/http-streaming": "3.13.2",
"@videojs/vhs-utils": "^4.0.0",
"@videojs/http-streaming": "^3.14.2",
"@videojs/vhs-utils": "^4.1.1",
"@videojs/xhr": "2.7.0",
"aes-decrypter": "^4.0.1",
"aes-decrypter": "^4.0.2",
"global": "4.4.0",
"m3u8-parser": "^7.1.0",
"m3u8-parser": "^7.2.0",
"mpd-parser": "^1.2.2",
"mux.js": "^7.0.1",
"safe-json-parse": "4.0.0",
"videojs-contrib-quality-levels": "4.1.0",
"videojs-font": "4.2.0",
"videojs-vtt.js": "0.15.5"
},
"dependencies": {
"@videojs/http-streaming": {
"version": "3.13.2",
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.13.2.tgz",
"integrity": "sha512-eCfQp61w00hg7Y9npmLnsJ6UvDF+SsFYzu7mQJgVXxWpVm9AthYWA3KQEKA7F7Sy6yzlm/Sps8BHs5ItelNZgQ==",
"requires": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "4.0.0",
"aes-decrypter": "4.0.1",
"global": "^4.4.0",
"m3u8-parser": "^7.1.0",
"mpd-parser": "^1.3.0",
"mux.js": "7.0.3",
"video.js": "^7 || ^8"
},
"dependencies": {
"@videojs/vhs-utils": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.0.0.tgz",
"integrity": "sha512-xJp7Yd4jMLwje2vHCUmi8MOUU76nxiwII3z4Eg3Ucb+6rrkFVGosrXlMgGnaLjq724j3wzNElRZ71D/CKrTtxg==",
"requires": {
"@babel/runtime": "^7.12.5",
"global": "^4.4.0",
"url-toolkit": "^2.2.1"
}
},
"aes-decrypter": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.1.tgz",
"integrity": "sha512-H1nh/P9VZXUf17AA5NQfJML88CFjVBDuGkp5zDHa7oEhYN9TTpNLJknRY1ie0iSKWlDf6JRnJKaZVDSQdPy6Cg==",
"requires": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.5",
"global": "^4.4.0",
"pkcs7": "^1.0.4"
},
"dependencies": {
"@videojs/vhs-utils": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
"integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
"requires": {
"@babel/runtime": "^7.12.5",
"global": "^4.4.0",
"url-toolkit": "^2.2.1"
}
}
}
}
}
}
}
},
"videojs-contrib-quality-levels": {

View File

@ -1,7 +1,7 @@
{
"name": "video.js",
"description": "An HTML5 video player that supports HLS and DASH with a common API and skin.",
"version": "8.18.0",
"version": "8.19.1",
"main": "./dist/video.cjs.js",
"module": "./dist/video.es.js",
"style": "./dist/video-js.css",
@ -86,13 +86,13 @@
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/http-streaming": "^3.14.1",
"@videojs/http-streaming": "^3.15.0",
"@videojs/vhs-utils": "^4.1.1",
"@videojs/xhr": "2.7.0",
"aes-decrypter": "^4.0.2",
"global": "4.4.0",
"m3u8-parser": "^7.2.0",
"mpd-parser": "^1.2.2",
"mpd-parser": "^1.3.1",
"mux.js": "^7.0.1",
"videojs-contrib-quality-levels": "4.1.0",
"videojs-font": "4.2.0",

View File

@ -2003,7 +2003,8 @@ class Player extends Component {
}
/**
* Handle a double-click on the media element to enter/exit fullscreen
* Handle a double-click on the media element to enter/exit fullscreen,
* or exit documentPictureInPicture mode
*
* @param {Event} event
* the event that caused this function to trigger
@ -2045,7 +2046,12 @@ class Player extends Component {
) {
this.options_.userActions.doubleClick.call(this, event);
} else if (this.isInPictureInPicture() && !document.pictureInPictureElement) {
// Checking the presence of `window.documentPictureInPicture.window` complicates
// tests, checking `document.pictureInPictureElement` also works. It wouldn't
// be null in regular picture in picture.
// Exit picture in picture mode. This gesture can't trigger pip on the main window.
this.exitPictureInPicture();
} else if (this.isFullscreen()) {
this.exitFullscreen();
} else {
@ -3704,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.
*/

View File

@ -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}.

View File

@ -151,6 +151,19 @@ QUnit.test('by default, double-click opens fullscreen', function(assert) {
assert.strictEqual(this.player.exitFullscreen.callCount, 1, 'has exited fullscreen');
});
QUnit.test('in document picture in picture mode, double-click exits pip', function(assert) {
this.player.isInPictureInPicture = () => true;
this.player.exitPictureInPicture = sinon.spy();
this.player.requestFullscreen = sinon.spy();
this.player.exitFullscreen = sinon.spy();
this.player.handleTechDoubleClick_({target: this.player.tech_.el_});
assert.strictEqual(this.player.exitPictureInPicture.callCount, 1, 'has exited pip once');
assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not entered fullscreen');
assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
});
QUnit.test('when controls are disabled, double-click does nothing', function(assert) {
let fullscreen = false;

View File

@ -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();
});

View File

@ -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');
});

View File

@ -111,6 +111,8 @@ class TechFaker extends Tech {
}
return 'movie.mp4';
}
addSourceElement() {}
removeSourceElement() {}
load() {
}
currentSrc() {