mirror of
				https://github.com/videojs/video.js.git
				synced 2025-10-31 00:08:01 +02:00 
			
		
		
		
	feat: adds disablePictureInPicture method to the player API. (#6378)
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							8c66c58346
						
					
				
				
					commit
					dbd5203a0b
				
			| @@ -52,7 +52,9 @@ export class VjsPlayerComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Don't forget to include the Video.js CSS, located at `video.js/dist/video-js.css`. | ||||
|  | ||||
| ```css | ||||
| /* vjs-player.component.css */ | ||||
| @import '~video.js/dist/video-js.css'; | ||||
|   | ||||
| @@ -13,6 +13,7 @@ This default value is hardcoded as a default to the localize method in the SeekB | ||||
| ## Status of translations | ||||
|  | ||||
| <!-- START langtable --> | ||||
|  | ||||
| | Language file           | Missing translations                                                                | | ||||
| | ----------------------- | ----------------------------------------------------------------------------------- | | ||||
| | ar.json (missing 3)     | progress bar timing: currentTime={1} duration={2}                                   | | ||||
| @@ -941,4 +942,5 @@ This default value is hardcoded as a default to the localize method in the SeekB | ||||
| |                         | Picture-in-Picture                                                                  | | ||||
| | zh-TW.json (missing 2)  | Exit Picture-in-Picture                                                             | | ||||
| |                         | Picture-in-Picture                                                                  | | ||||
|  | ||||
| <!-- END langtable --> | ||||
|   | ||||
							
								
								
									
										72
									
								
								sandbox/pip-disabled.html.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								sandbox/pip-disabled.html.example
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|   <meta charset="utf-8" /> | ||||
|   <title>Video.js Sandbox</title> | ||||
|   <link href="../dist/video-js.css" rel="stylesheet" type="text/css"> | ||||
|   <script src="../dist/video.js"></script> | ||||
| </head> | ||||
| <body> | ||||
|   <div style="background-color:#eee; border: 1px solid #777; padding: 10px; margin-bottom: 20px; font-size: .8em; line-height: 1.5em; font-family: Verdana, sans-serif;"> | ||||
|     <p>You can use /sandbox/ for writing and testing your own code. Nothing in /sandbox/ will get checked into the repo, except files that end in .example (so don't edit or add those files). To get started run `npm start` and open the index.html</p> | ||||
|     <pre>npm start</pre> | ||||
|     <pre>open http://localhost:9999/sandbox/index.html</pre> | ||||
|   </div> | ||||
|  | ||||
|   <video-js | ||||
|     id="vid1" | ||||
|     controls | ||||
|     disablepictureinpicture | ||||
|     preload="none" | ||||
|     width="640" | ||||
|     height="264" | ||||
|     poster="http://vjs.zencdn.net/v/oceans.png"> | ||||
|     <source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4"> | ||||
|     <source src="http://vjs.zencdn.net/v/oceans.webm" type="video/webm"> | ||||
|     <source src="http://vjs.zencdn.net/v/oceans.ogv" type="video/ogg"> | ||||
|     <track kind="captions" src="../docs/examples/shared/example-captions.vtt" srclang="en" label="English"> | ||||
|     <p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p> | ||||
|   </video-js> | ||||
|   <p> | ||||
|     PiP disabled via <b>disablepictureinpicture</b> attribute. | ||||
|   </p> | ||||
|   <video-js | ||||
|       id="vid2" | ||||
|       controls | ||||
|       preload="auto" | ||||
|       width="640" | ||||
|       height="264" | ||||
|       poster="http://vjs.zencdn.net/v/oceans.png"> | ||||
|       <source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4"> | ||||
|       <source src="http://vjs.zencdn.net/v/oceans.webm" type="video/webm"> | ||||
|       <source src="http://vjs.zencdn.net/v/oceans.ogv" type="video/ogg"> | ||||
|       <track kind="captions" src="../docs/examples/shared/example-captions.vtt" srclang="en" label="English"> | ||||
|       <p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p> | ||||
|     </video-js> | ||||
|     <p> | ||||
|       PiP disabled via <b>disablePictureInPicture</b> player option. | ||||
|     </p> | ||||
|     <button id="PiPToggleBtn">call disablePictureInPicture(false) for the player 2</button> | ||||
|   <script> | ||||
|     var pipDisabled = true; | ||||
|     var vid = document.getElementById('vid1'); | ||||
|     var player = videojs(vid); | ||||
|  | ||||
|     var vid2 = document.getElementById('vid2'); | ||||
|     var player2 = videojs(vid2, { | ||||
|       controlBar: { | ||||
|         pictureInPictureToggle: true | ||||
|       }, | ||||
|       disablePictureInPicture: true | ||||
|     }); | ||||
|  | ||||
|     var pipToggle = document.getElementById('PiPToggleBtn'); | ||||
|     pipToggle.addEventListener('click', function() { | ||||
|       pipToggle.innerText = 'call disablePictureInPicture(' + pipDisabled + ') for the player 2'; | ||||
|       pipDisabled = !pipDisabled; | ||||
|       player2.disablePictureInPicture(pipDisabled); | ||||
|     }); | ||||
|   </script> | ||||
|  | ||||
| </body> | ||||
| </html> | ||||
| @@ -27,13 +27,10 @@ class PictureInPictureToggle extends Button { | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|     this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], this.handlePictureInPictureChange); | ||||
|     this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], this.handlePictureInPictureEnabledChange); | ||||
|  | ||||
|     // TODO: Activate button on player loadedmetadata event. | ||||
|     // TODO: Deactivate button on player emptied event. | ||||
|     // TODO: Deactivate button if disablepictureinpicture attribute is present. | ||||
|     if (!document.pictureInPictureEnabled) { | ||||
|       this.disable(); | ||||
|     } | ||||
|     this.disable(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -46,6 +43,18 @@ class PictureInPictureToggle extends Button { | ||||
|     return `vjs-picture-in-picture-control ${super.buildCSSClass()}`; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Enables or disables button based on document.pictureInPictureEnabled property value | ||||
|    * or on value returned by player.disablePictureInPicture() method. | ||||
|    */ | ||||
|   handlePictureInPictureEnabledChange() { | ||||
|     if (!document.pictureInPictureEnabled || this.player_.disablePictureInPicture()) { | ||||
|       this.disable(); | ||||
|     } else { | ||||
|       this.enable(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly. | ||||
|    * | ||||
| @@ -62,6 +71,7 @@ class PictureInPictureToggle extends Button { | ||||
|     } else { | ||||
|       this.controlText('Picture-in-Picture'); | ||||
|     } | ||||
|     this.handlePictureInPictureEnabledChange(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|   | ||||
| @@ -1119,6 +1119,7 @@ class Player extends Component { | ||||
|       'playsinline': this.options_.playsinline, | ||||
|       'preload': this.options_.preload, | ||||
|       'loop': this.options_.loop, | ||||
|       'disablePictureInPicture': this.options_.disablePictureInPicture, | ||||
|       'muted': this.options_.muted, | ||||
|       'poster': this.poster(), | ||||
|       'language': this.language(), | ||||
| @@ -2982,6 +2983,22 @@ class Player extends Component { | ||||
|     this.trigger('exitFullWindow'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Disable Picture-in-Picture mode. | ||||
|    * | ||||
|    * @param {boolean} value | ||||
|    *                  - true will disable Picture-in-Picture mode | ||||
|    *                  - false will enable Picture-in-Picture mode | ||||
|    */ | ||||
|   disablePictureInPicture(value) { | ||||
|     if (value === undefined) { | ||||
|       return this.techGet_('disablePictureInPicture'); | ||||
|     } | ||||
|     this.techCall_('setDisablePictureInPicture', value); | ||||
|     this.options_.disablePictureInPicture = value; | ||||
|     this.trigger('disablepictureinpicturechanged'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Check if the player is in Picture-in-Picture mode or tell the player that it | ||||
|    * is or is not in Picture-in-Picture mode. | ||||
|   | ||||
| @@ -405,6 +405,10 @@ class Html5 extends Tech { | ||||
|       Dom.setAttribute(el, 'preload', this.options_.preload); | ||||
|     } | ||||
|  | ||||
|     if (this.options_.disablePictureInPicture !== undefined) { | ||||
|       el.disablePictureInPicture = this.options_.disablePictureInPicture; | ||||
|     } | ||||
|  | ||||
|     // Update specific tag settings, in case they were overridden | ||||
|     // `autoplay` has to be *last* so that `muted` and `playsinline` are present | ||||
|     // when iOS/Safari or other browsers attempt to autoplay. | ||||
| @@ -1526,8 +1530,8 @@ Html5.resetMediaElement = function(el) { | ||||
| // Wrap native properties with a getter | ||||
| // The list is as followed | ||||
| // paused, currentTime, buffered, volume, poster, preload, error, seeking | ||||
| // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState | ||||
| // readyState, videoWidth, videoHeight, crossOrigin | ||||
| // seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture | ||||
| // played, networkState, readyState, videoWidth, videoHeight, crossOrigin | ||||
| [ | ||||
|   /** | ||||
|    * Get the value of `paused` from the media element. `paused` indicates whether the media element | ||||
| @@ -1699,6 +1703,19 @@ Html5.resetMediaElement = function(el) { | ||||
|    */ | ||||
|   'defaultPlaybackRate', | ||||
|  | ||||
|   /** | ||||
|    * Get the value of 'disablePictureInPicture' from the video element. | ||||
|    * | ||||
|    * @method Html5#disablePictureInPicture | ||||
|    * @return {boolean} value | ||||
|    *         - The value of `disablePictureInPicture` from the video element. | ||||
|    *         - True indicates that the video can't be played in Picture-In-Picture mode | ||||
|    *         - False indicates that the video can be played in Picture-In-Picture mode | ||||
|    * | ||||
|    * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip} | ||||
|    */ | ||||
|   'disablePictureInPicture', | ||||
|  | ||||
|   /** | ||||
|    * Get the value of `played` from the media element. `played` returns a `TimeRange` | ||||
|    * object representing points in the media timeline that have been played. | ||||
| @@ -1796,7 +1813,8 @@ Html5.resetMediaElement = function(el) { | ||||
| // Wrap native properties with a setter in this format: | ||||
| // set + toTitleCase(name) | ||||
| // The list is as follows: | ||||
| // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate, setCrossOrigin | ||||
| // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate, | ||||
| // setDisablePictureInPicture, setCrossOrigin | ||||
| [ | ||||
|   /** | ||||
|    * Set the value of `volume` on the media element. `volume` indicates the current | ||||
| @@ -1888,6 +1906,18 @@ Html5.resetMediaElement = function(el) { | ||||
|    */ | ||||
|   'defaultPlaybackRate', | ||||
|  | ||||
|   /** | ||||
|    * Prevents the browser from suggesting a Picture-in-Picture context menu | ||||
|    * or to request Picture-in-Picture automatically in some cases. | ||||
|    * | ||||
|    * @method Html5#setDisablePictureInPicture | ||||
|    * @param {boolean} value | ||||
|    *         The true value will disable Picture-in-Picture mode. | ||||
|    * | ||||
|    * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip} | ||||
|    */ | ||||
|   'disablePictureInPicture', | ||||
|  | ||||
|   /** | ||||
|    * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates | ||||
|    * to the browser that should sent the cookies along with the requests for the | ||||
|   | ||||
| @@ -788,6 +788,22 @@ class Tech extends Component { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * A method to check for the presence of the 'disablePictureInPicture' <video> property. | ||||
|    * | ||||
|    * @abstract | ||||
|    */ | ||||
|   disablePictureInPicture() { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * A method to set or unset the 'disablePictureInPicture' <video> property. | ||||
|    * | ||||
|    * @abstract | ||||
|    */ | ||||
|   setDisablePictureInPicture() {} | ||||
|  | ||||
|   /** | ||||
|    * A method to set a poster from a `Tech`. | ||||
|    * | ||||
|   | ||||
| @@ -39,6 +39,7 @@ QUnit.test('should be able to access expected player API methods', function(asse | ||||
|   assert.ok(player.textTracks, 'textTracks exists'); | ||||
|   assert.ok(player.requestFullscreen, 'requestFullscreen exists'); | ||||
|   assert.ok(player.exitFullscreen, 'exitFullscreen exists'); | ||||
|   assert.ok(player.disablePictureInPicture, 'disablePictureInPicture exists'); | ||||
|   assert.ok(player.requestPictureInPicture, 'requestPictureInPicture exists'); | ||||
|   assert.ok(player.exitPictureInPicture, 'exitPictureInPicture exists'); | ||||
|   assert.ok(player.playbackRate, 'playbackRate exists'); | ||||
|   | ||||
| @@ -169,6 +169,52 @@ QUnit.test('Picture-in-Picture control text should be correct when enterpicturei | ||||
|   pictureInPictureToggle.dispose(); | ||||
| }); | ||||
|  | ||||
| QUnit.test('Picture-in-Picture control enabled property value should be correct when enterpictureinpicture and leavepictureinpicture are triggered', function(assert) { | ||||
|   const player = TestHelpers.makePlayer(); | ||||
|   const pictureInPictureToggle = new PictureInPictureToggle(player); | ||||
|  | ||||
|   assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after creation'); | ||||
|  | ||||
|   if ('pictureInPictureEnabled' in document) { | ||||
|     player.isInPictureInPicture(true); | ||||
|     player.trigger('enterpictureinpicture'); | ||||
|     assert.equal(pictureInPictureToggle.enabled_, true, 'pictureInPictureToggle button should be enabled after triggering an enterpictureinpicture event'); | ||||
|  | ||||
|     player.isInPictureInPicture(false); | ||||
|     player.trigger('leavepictureinpicture'); | ||||
|     assert.equal(pictureInPictureToggle.enabled_, true, 'pictureInPictureToggle button should be enabled after triggering an leavepictureinpicture event'); | ||||
|   } else { | ||||
|     player.isInPictureInPicture(true); | ||||
|     player.trigger('enterpictureinpicture'); | ||||
|     assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after triggering an enterpictureinpicture event'); | ||||
|  | ||||
|     player.isInPictureInPicture(false); | ||||
|     player.trigger('leavepictureinpicture'); | ||||
|     assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after triggering an leavepictureinpicture event'); | ||||
|   } | ||||
|  | ||||
|   player.dispose(); | ||||
|   pictureInPictureToggle.dispose(); | ||||
| }); | ||||
|  | ||||
| QUnit.test('Picture-in-Picture control enabled property value should be correct when loadedmetadata is triggered', function(assert) { | ||||
|   const player = TestHelpers.makePlayer(); | ||||
|   const pictureInPictureToggle = new PictureInPictureToggle(player); | ||||
|  | ||||
|   assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after creation'); | ||||
|  | ||||
|   if ('pictureInPictureEnabled' in document) { | ||||
|     player.trigger('loadedmetadata'); | ||||
|     assert.equal(pictureInPictureToggle.enabled_, true, 'pictureInPictureToggle button should be enabled after triggering an loadedmetadata event'); | ||||
|   } else { | ||||
|     player.trigger('loadedmetadata'); | ||||
|     assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after triggering an loadedmetadata event'); | ||||
|   } | ||||
|  | ||||
|   player.dispose(); | ||||
|   pictureInPictureToggle.dispose(); | ||||
| }); | ||||
|  | ||||
| QUnit.test('Fullscreen control text should be correct when fullscreenchange is triggered', function(assert) { | ||||
|   const player = TestHelpers.makePlayer({controlBar: false}); | ||||
|   const fullscreentoggle = new FullscreenToggle(player); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user