1. Introduction
This section is non-normative.
Many users want to continue consuming media while they interact with other content, sites, or applications on their device. A common UI affordance for this type of activity is Picture-in-Picture (PiP), where the video is contained in a separate miniature window that is always on top of other windows. Picture-in-Picture is a common platform-level feature among desktop and mobile OSs.
This specification aims to allow websites to initiate and control this behavior by exposing the following sets of properties to the API:
-
Notify the website when it enters and leave Picture-in-Picture mode.
-
Allow the website to trigger Picture-in-Picture via a user gesture on a video element.
-
Allow the website to exit Picture-in-Picture.
-
Allow the website to check if Picture-in-Picture can be triggered.
The
proposed
Picture-in-Picture
API
is
very
similar
to
[Fullscreen]
as
they
have
similar
properties.
The
API
only
applies
on
HTMLVideoElement
at
the
moment
but
is
meant
to
be
extensible.
2. Examples
2.1. Add a custom Picture-in-Picture button
< video id = "video" src = "https://example.com/file.mp4" ></ video > < button id = "pipButton" ></ button > < script > // Hide button if Picture-in-Picture is not supported or disabled. pipButton. hidden= ! document. pictureInPictureEnabled|| video. disablePictureInPicture; pipButton. addEventListener( 'click' , function () { // If there is no element in Picture-in-Picture yet, let’s request // Picture-in-Picture for the video, otherwise leave it. if ( ! document. pictureInPictureElement) { video. requestPictureInPicture() . catch ( error=> { // Video failed to enter Picture-in-Picture mode. }); } else { document. exitPictureInPicture() . catch ( error=> { // Video failed to leave Picture-in-Picture mode. }); } }); </ script >
2.2. Monitor video Picture-in-Picture changes
< video id = "video" src = "https://example.com/file.mp4" ></ video > < script > video. addEventListener( 'enterpictureinpicture' , function () { // Video entered Picture-in-Picture mode. }); video. addEventListener( 'leavepictureinpicture' , function () { // Video left Picture-in-Picture mode. }); </ script >
2.3. Update video size based on Picture-in-Picture window size changes
< video id = "video" src = "https://example.com/file.mp4" ></ video > < button id = "pipButton" ></ button > < script > pipButton. addEventListener( 'click' , function () { video. requestPictureInPicture() . then( pipWindow=> { updateVideoSize( pipWindow. width, pipWindow. height); pipWindow. addEventListener( 'resize' , function ( event) { updateVideoSize( pipWindow. width, pipWindow. height); }); }); }); function updateVideoSize( width, height) { // TODO: Update video size based on pip window width and height. } </ script >
3. Concepts
3.1. Request Picture-in-Picture
When the request Picture-in-Picture algorithm with video is invoked, the user agent MUST run the following steps:
-
If Picture-in-Picture support is
false
, throw aNotSupportedError
and abort these steps. -
If document is not allowed to use the policy-controlled feature named
"picture-in-picture"
, throw aSecurityError
and abort these steps. -
If video ’s
readyState
attribute isHAVE_NOTHING
, throw aInvalidStateError
and abort these steps. -
If video has no video track, throw a
InvalidStateError
and abort these steps. -
OPTIONALLY, if the
disablePictureInPicture
attribute is present on video , throw aInvalidStateError
and abort these steps. -
If the algorithm is not triggered by user activation , throw a
NotAllowedError
and abort these steps. -
If video is
pictureInPictureElement
, abort these steps. -
Set
pictureInPictureElement
to video . -
Let Picture-in-Picture window be a new instance of
PictureInPictureWindow
associated withpictureInPictureElement
. -
Queue a task to fire an event with the name
enterpictureinpicture
at the video with itsbubbles
attribute initialized to true.
It is RECOMMENDED that the video frames are not rendered in the page and in the Picture-in-Picture window at the same time but if they are, they MUST be kept in sync.
When a video is played in Picture-in-Picture, the states SHOULD transition as if it was played inline. That means that the events SHOULD fire at the same time, calling methods SHOULD have the same behaviour, etc. However, the user agent MAY transition out of Picture-in-Picture when the video element enters a state that is considered not compatible with Picture-in-Picture.
3.2. Exit Picture-in-Picture
When the exit Picture-in-Picture algorithm is invoked, the user agent MUST run the following steps:
-
If
pictureInPictureElement
is null, throw aInvalidStateError
and abort these steps. -
Run the close window algorithm with the Picture-in-Picture window associated with
pictureInPictureElement
. -
Unset
pictureInPictureElement
. -
Queue a task to fire an event with the name
leavepictureinpicture
at the video with itsbubbles
attribute initialized to true.
It is NOT RECOMMENDED that video playback state changes when the exit Picture-in-Picture algorithm is invoked. The website SHOULD be in control of the experience if it is website initiated. However, user agent MAY expose Picture-in-Picture window controls that change video playback state (e.g. pause).
3.3. Disable Picture-in-Picture
Some
pages
may
want
to
disable
Picture-in-Picture
for
a
video
element.
To
support
this,
a
new
disablePictureInPicture
attribute
is
added
to
the
list
of
content
attributes
for
video
elements.
A
corresponding
disablePictureInPicture
IDL
attribute
which
reflects
the
value
of
element’s
disablePictureInPicture
content
attribute
is
added
to
the
HTMLVideoElement
interface.
The
disablePictureInPicture
IDL
attribute
MUST
reflect
the
content
attribute
of
the
same
name.
If
the
disablePictureInPicture
attribute
is
present
on
the
video
element,
the
user
agent
SHOULD
NOT
play
the
video
element
in
Picture-in-Picture
or
present
any
UI
to
do
so.
When
the
disablePictureInPicture
attribute
is
added
to
a
video
element,
the
user
agent
SHOULD
run
these
steps:
-
Reject any pending promises returned by the
requestPictureInPicture()
method withInvalidStateError
. -
If video is
pictureInPictureElement
, run the exit Picture-in-Picture algorithm .
3.4. Interaction with Remote Playback
The [Remote-Playback] specification defines a local playback device and a local playback state . For the purpose of Picture-in-Picture, the playback is local and regardless of whether it is played in page or in Picture-in-Picture.
3.5. Interaction with Media Session
The API will have to be used with the [MediaSession] API for customizing the available controls on the Picture-in-Picture window.
3.6. Interaction with Page Visibility
The
[Page-Visibility]
specification
defines
a
visibilityState
attribute
used
to
determine
the
visibility
state
of
a
top
level
browsing
context.
For
the
purpose
of
Picture-in-Picture,
the
visibilityState
attribute
is
always
"visible"
when
pictureInPictureElement
is
set
and
the
Operating
System
lock
screen
is
not
shown.
3.7. One Picture-in-Picture window
Operating
systems
with
a
Picture-in-Picture
API
usually
restricts
Picture-in-Picture
to
only
one
window.
Whether
only
one
window
is
allowed
in
Picture-in-Picture
will
be
left
to
the
implementation
and
the
platform.
However,
because
of
the
one
Picture-in-Picture
window
limitation,
the
specification
assumes
that
a
given
Document
can
only
have
one
Picture-in-Picture
window.
What happens when there is a Picture-in-Picture request while a window is already in Picture-in-Picture will be left as an implementation details: the current Picture-in-Picture window could be closed, the Picture-in-Picture request could be rejected or even two Picture-in-Picture windows can be created. Regardless, the User Agent will have to fire the appropriate events in order to notify the website of the Picture-in-Picture status changes.
4. API
4.1.
Extensions
to
HTMLVideoElement
partial interface HTMLVideoElement { [NewObject ]Promise <PictureInPictureWindow >();
requestPictureInPicture attribute EventHandler ;
onenterpictureinpicture attribute EventHandler ; [
onleavepictureinpicture CEReactions ]attribute boolean ; };
disablePictureInPicture
The
requestPictureInPicture()
method,
when
invoked,
MUST
return
a
new
promise
promise
and
run
the
following
steps
in
parallel
:
-
Let video be the requested video.
-
Run the request Picture-in-Picture algorithm with video .
-
If the previous step threw an exception, reject promise with that exception and abort these steps.
-
Return promise with the Picture-in-Picture window associated with
pictureInPictureElement
.
4.2.
Extensions
to
Document
partial interface Document {readonly attribute boolean ; [
pictureInPictureEnabled NewObject ]Promise <void >(); };
exitPictureInPicture
The
pictureInPictureEnabled
attribute’s
getter
must
return
true
if
Picture-in-Picture
support
is
true
and
the
context
object
is
allowed
to
use
the
feature
indicated
by
attribute
name
picture-in-picture
,
and
false
otherwise.
Picture-in-Picture
support
is
true
if
there
is
no
previously-established
user
preference,
restrictions,
or
platform
limitation,
and
false
otherwise.
The
exitPictureInPicture()
method,
when
invoked,
MUST
return
a
new
promise
promise
and
run
the
following
steps
in
parallel
:
-
Run the exit Picture-in-Picture algorithm .
-
If the previous step threw an exception, reject promise with that exception and abort these steps.
-
Return promise .
4.3.
Extension
to
DocumentOrShadowRoot
partial interface DocumentOrShadowRoot {readonly attribute Element ?; };
pictureInPictureElement
The
pictureInPictureElement
attribute’s
getter
must
run
these
steps:
-
If the context object is not connected , return null and abort these steps.
-
Let candidate be the result of retargeting Picture-in-Picture element against the context object .
-
If candidate and the context object are in the same tree , return candidate and abort these steps.
-
Return null.
4.4.
Interface
PictureInPictureWindow
interface :
PictureInPictureWindow EventTarget {readonly attribute long ;
width readonly attribute long ;
height attribute EventHandler ; };
onresize
A
PictureInPictureWindow
instance
represents
a
Picture-in-Picture
window
associated
with
an
HTMLVideoElement
.
When
instantiated,
an
instance
of
PictureInPictureWindow
has
its
state
set
to
opened
.
When
the
close
window
algorithm
with
an
instance
of
PictureInPictureWindow
is
invoked,
its
state
is
set
to
closed
.
The
width
attribute
MUST
return
the
width
in
CSS
pixels
of
the
Picture-in-Picture
window
associated
with
pictureInPictureElement
if
the
state
is
opened
.
Otherwise,
it
MUST
return
0.
The
height
attribute
MUST
return
the
height
in
CSS
pixels
of
the
Picture-in-Picture
window
associated
with
pictureInPictureElement
if
the
state
is
opened
.
Otherwise,
it
MUST
return
0.
When
the
size
of
the
Picture-in-Picture
window
associated
with
pictureInPictureElement
changes,
the
user
agent
MUST
queue
a
task
to
fire
an
event
with
the
name
resize
at
pictureInPictureElement
.
4.5. Event types
-
enterpictureinpicture
-
Fired on a
HTMLVideoElement
when it enters Picture-in-Picture. -
leavepictureinpicture
-
Fired on a
HTMLVideoElement
when it leaves Picture-in-Picture. -
resize
-
Fired on a
PictureInPictureWindow
when it changes size.
4.6. Task source
The task source for all the tasks queued in this specification is the media element event task source of the video element in question.
5. Security considerations
This section is non-normative.
The
API
applies
only
to
HTMLVideoElement
in
order
to
start
on
a
minimal
viable
product
that
has
limited
security
issues.
Later
versions
of
this
specification
may
allow
PIP-ing
arbitrary
HTML
content.
5.1. Feature Policy
This
specification
defines
a
policy-controlled
feature
that
controls
whether
the
request
Picture-in-Picture
algorithm
may
return
a
SecurityError
and
whether
pictureInPictureEnabled
is
true
or
false
.
The
feature
name
for
this
feature
is
"picture-in-picture"
.
The
default
allowlist
for
this
feature
is
*
.