mirror of
				https://github.com/videojs/video.js.git
				synced 2025-10-31 00:08:01 +02:00 
			
		
		
		
	feat: add Picture-in-Picture API methods (#6001)
Following #5824, this PR adds support for some Picture-in-Picture methods described in the spec and article. It also makes sure that we can listen to the enterpictureinpicture and leavepictureinpicture events on the player.
This commit is contained in:
		
				
					committed by
					
						 Gary Katsevman
						Gary Katsevman
					
				
			
			
				
	
			
			
			
						parent
						
							882432e99d
						
					
				
				
					commit
					83541dceeb
				
			
							
								
								
									
										143
									
								
								src/js/player.js
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								src/js/player.js
									
									
									
									
									
								
							| @@ -1076,7 +1076,8 @@ class Player extends Component { | ||||
|       'playerElIngest': this.playerElIngest_ || false, | ||||
|       'vtt.js': this.options_['vtt.js'], | ||||
|       'canOverridePoster': !!this.options_.techCanOverridePoster, | ||||
|       'enableSourceset': this.options_.enableSourceset | ||||
|       'enableSourceset': this.options_.enableSourceset, | ||||
|       'Promise': this.options_.Promise | ||||
|     }; | ||||
|  | ||||
|     TRACK_TYPES.names.forEach((name) => { | ||||
| @@ -1139,6 +1140,8 @@ class Player extends Component { | ||||
|     this.on(this.tech_, 'pause', this.handleTechPause_); | ||||
|     this.on(this.tech_, 'durationchange', this.handleTechDurationChange_); | ||||
|     this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); | ||||
|     this.on(this.tech_, 'enterpictureinpicture', this.handleTechEnterPictureInPicture_); | ||||
|     this.on(this.tech_, 'leavepictureinpicture', this.handleTechLeavePictureInPicture_); | ||||
|     this.on(this.tech_, 'error', this.handleTechError_); | ||||
|     this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); | ||||
|     this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); | ||||
| @@ -2043,6 +2046,61 @@ class Player extends Component { | ||||
|     this.trigger('fullscreenchange'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * @private | ||||
|    */ | ||||
|   togglePictureInPictureClass_() { | ||||
|     if (this.isInPictureInPicture()) { | ||||
|       this.addClass('vjs-picture-in-picture'); | ||||
|     } else { | ||||
|       this.removeClass('vjs-picture-in-picture'); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Handle Tech Enter Picture-in-Picture. | ||||
|    * | ||||
|    * @param {EventTarget~Event} event | ||||
|    *        the enterpictureinpicture event that triggered this function | ||||
|    * | ||||
|    * @private | ||||
|    * @listens Tech#enterpictureinpicture | ||||
|    * @fires Player#pictureinpicturechange | ||||
|    */ | ||||
|   handleTechEnterPictureInPicture_(event) { | ||||
|     this.isInPictureInPicture(true); | ||||
|  | ||||
|     /** | ||||
|      * Fired when going in and out of Picture-in-Picture. | ||||
|      * | ||||
|      * @event Player#pictureinpicturechange | ||||
|      * @type {EventTarget~Event} | ||||
|      */ | ||||
|     this.trigger('pictureinpicturechange'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Handle Tech Leave Picture-in-Picture. | ||||
|    * | ||||
|    * @param {EventTarget~Event} event | ||||
|    *        the leavepictureinpicture event that triggered this function | ||||
|    * | ||||
|    * @private | ||||
|    * @listens Tech#leavepictureinpicture | ||||
|    * @fires Player#pictureinpicturechange | ||||
|    */ | ||||
|   handleTechLeavePictureInPicture_(event) { | ||||
|     this.isInPictureInPicture(false); | ||||
|  | ||||
|     /** | ||||
|      * Fired when going in and out of Picture-in-Picture. | ||||
|      * | ||||
|      * @event Player#pictureinpicturechange | ||||
|      * @type {EventTarget~Event} | ||||
|      */ | ||||
|     this.trigger('pictureinpicturechange'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Fires when an error occurred during the loading of an audio/video. | ||||
|    * | ||||
| @@ -2801,6 +2859,89 @@ class Player extends Component { | ||||
|     this.trigger('exitFullWindow'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 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. | ||||
|    * | ||||
|    * @param  {boolean} [isPiP] | ||||
|    *         Set the players current Picture-in-Picture state | ||||
|    * | ||||
|    * @return {boolean} | ||||
|    *         - true if Picture-in-Picture is on and getting | ||||
|    *         - false if Picture-in-Picture is off and getting | ||||
|    */ | ||||
|   isInPictureInPicture(isPiP) { | ||||
|     if (isPiP !== undefined) { | ||||
|       this.isInPictureInPicture_ = !!isPiP; | ||||
|       this.togglePictureInPictureClass_(); | ||||
|       return; | ||||
|     } | ||||
|     return !!this.isInPictureInPicture_; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Create a floating video window always on top of other windows so that users may | ||||
|    * continue consuming media while they interact with other content sites, or | ||||
|    * applications on their device. | ||||
|    * | ||||
|    * @see [Spec]{@link https://wicg.github.io/picture-in-picture} | ||||
|    * | ||||
|    * @fires Player#pictureinpicturechange | ||||
|    * | ||||
|    * @return {Promise} | ||||
|    *         A promise with a Picture-in-Picture window. | ||||
|    */ | ||||
|   requestPictureInPicture() { | ||||
|     if ('pictureInPictureEnabled' in document) { | ||||
|       return this.techGet_('requestPictureInPicture'); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Exit Picture-in-Picture mode. | ||||
|    * | ||||
|    * @see [Spec]{@link https://wicg.github.io/picture-in-picture} | ||||
|    * | ||||
|    * @fires Player#pictureinpicturechange | ||||
|    * | ||||
|    * @return {Promise} | ||||
|    *         A promise. | ||||
|    */ | ||||
|   exitPictureInPicture() { | ||||
|     if ('pictureInPictureEnabled' in document) { | ||||
|       return document.exitPictureInPicture(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * This gets called when a `Player` gains focus via a `focus` event. | ||||
|    * Turns on listening for `keydown` events. When they happen it | ||||
|    * calls `this.handleKeyPress`. | ||||
|    * | ||||
|    * @param {EventTarget~Event} event | ||||
|    *        The `focus` event that caused this function to be called. | ||||
|    * | ||||
|    * @listens focus | ||||
|    */ | ||||
|   handleFocus(event) { | ||||
|     // call off first to make sure we don't keep adding keydown handlers | ||||
|     Events.off(document, 'keydown', this.boundHandleKeyPress_); | ||||
|     Events.on(document, 'keydown', this.boundHandleKeyPress_); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Called when a `Player` loses focus. Turns off the listener for | ||||
|    * `keydown` events. Which Stops `this.handleKeyPress` from getting called. | ||||
|    * | ||||
|    * @param {EventTarget~Event} event | ||||
|    *        The `blur` event that caused this function to be called. | ||||
|    * | ||||
|    * @listens blur | ||||
|    */ | ||||
|   handleBlur(event) { | ||||
|     Events.off(document, 'keydown', this.boundHandleKeyPress_); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Called when this Player has focus and a key gets pressed down, or when | ||||
|    * any Component of this player receives a key press that it doesn't handle. | ||||
|   | ||||
| @@ -659,6 +659,20 @@ class Html5 extends Tech { | ||||
|     this.el_.webkitExitFullScreen(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Create a floating video window always on top of other windows so that users may | ||||
|    * continue consuming media while they interact with other content sites, or | ||||
|    * applications on their device. | ||||
|    * | ||||
|    * @see [Spec]{@link https://wicg.github.io/picture-in-picture} | ||||
|    * | ||||
|    * @return {Promise} | ||||
|    *         A promise with a Picture-in-Picture window. | ||||
|    */ | ||||
|   requestPictureInPicture() { | ||||
|     return this.el_.requestPictureInPicture(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * A getter/setter for the `Html5` Tech's source object. | ||||
|    * > Note: Please use {@link Html5#setSource} | ||||
|   | ||||
| @@ -764,6 +764,28 @@ class Tech extends Component { | ||||
|     return {}; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Attempt to create a floating video window always on top of other windows | ||||
|    * so that users may continue consuming media while they interact with other | ||||
|    * content sites, or applications on their device. | ||||
|    * | ||||
|    * @see [Spec]{@link https://wicg.github.io/picture-in-picture} | ||||
|    * | ||||
|    * @return {Promise|undefined} | ||||
|    *         A promise with a Picture-in-Picture window if the browser supports | ||||
|    *         Promises (or one was passed in as an option). It returns undefined | ||||
|    *         otherwise. | ||||
|    * | ||||
|    * @abstract | ||||
|    */ | ||||
|   requestPictureInPicture() { | ||||
|     const PromiseClass = this.options_.Promise || window.Promise; | ||||
|  | ||||
|     if (PromiseClass) { | ||||
|       return PromiseClass.reject(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * A method to set a poster from a `Tech`. | ||||
|    * | ||||
|   | ||||
| @@ -39,6 +39,8 @@ 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.requestPictureInPicture, 'requestPictureInPicture exists'); | ||||
|   assert.ok(player.exitPictureInPicture, 'exitPictureInPicture exists'); | ||||
|   assert.ok(player.playbackRate, 'playbackRate exists'); | ||||
|   assert.ok(player.networkState, 'networkState exists'); | ||||
|   assert.ok(player.readyState, 'readyState exists'); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user