📄 useMediaControls¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 7 |
| 📦 Imports | 17 |
| 📊 Variables & Constants | 1 |
| ⚡ Async/Await Patterns | 2 |
| 🟢 Vue Composition API | 4 |
| 📐 Interfaces | 5 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/core/useMediaControls/index.ts
📦 Imports¶
| Name | Source |
|---|---|
EventHookOn |
@vueuse/shared |
Fn |
@vueuse/shared |
MaybeRef |
vue |
MaybeRefOrGetter |
vue |
ShallowRef |
vue |
ConfigurableDocument |
../_configurable |
createEventHook |
@vueuse/shared |
isObject |
@vueuse/shared |
toRef |
@vueuse/shared |
tryOnScopeDispose |
@vueuse/shared |
watchIgnorable |
@vueuse/shared |
shallowRef |
vue |
toValue |
vue |
watch |
vue |
watchEffect |
vue |
defaultDocument |
../_configurable |
useEventListener |
../useEventListener |
Variables & Constants¶
| Name | Type | Kind | Value | Exported |
|---|---|---|---|---|
defaultOptions |
UseMediaControlsOptions |
const | { src: '', tracks: [], } |
✗ |
Async/Await Patterns¶
| Type | Function | Await Expressions | Promise Chains |
|---|---|---|---|
| promise-chain | useMediaControls |
none | new Promise(...), el.requestPictureInPicture().then(resolve).catch, el.reques... |
| promise-chain | togglePictureInPicture |
none | new Promise(...), el.requestPictureInPicture().then(resolve).catch, el.reques... |
Vue Composition API¶
| Name | Type | Reactive Variables | Composables |
|---|---|---|---|
watch |
watch | none | none |
watch |
watch | none | none |
watch |
watch | none | none |
watch |
watch | none | none |
Functions¶
useMediaControls(target: MaybeRef<HTMLMediaElement | null | unde…, options: UseMediaControlsOptions): UseMediaControlsReturn¶
Parameters:
targetMaybeRef<HTMLMediaElement | null | undefined>optionsUseMediaControlsOptions
Returns: UseMediaControlsReturn
Calls:
toRef (from @vueuse/shared)shallowRef (from vue)BooleancreateEventHook (from @vueuse/shared)usingElRefdisableTrackel.requestPictureInPicture().then(resolve).catchdocument!.exitPictureInPicture().then(resolve).catchwatchEffect (from vue)toValue (from vue)Array.isArrayisObject (from @vueuse/shared)el.querySelectorAll('source').forEache.removesources.forEachdocument.createElementsource.setAttributeuseEventListener (from ../useEventListener)el.appendChildel.loadwatch (from vue)el.querySelectorAll('track').forEachtextTracks.forEachwatchIgnorable (from @vueuse/shared)el.play().catchplaybackErrorEvent.triggerel.pauseignoreCurrentTimeUpdatestimeRangeToArrayignorePlayingUpdatesstoptracksToArraytryOnScopeDispose (from @vueuse/shared)listeners.forEachlistener
Internal Comments:
// Events (x4)
/**
* Disables the specified track. If no track is specified then
* all tracks will be disabled
*
* @param track The id of the track to disable
*/ (x2)
/**
* Enables the specified track and disables the
* other tracks unless otherwise specified
*
* @param track The track of the id of the track to enable
* @param disableTracks Disable all other tracks
*/ (x2)
/**
* Toggle picture in picture mode for the player.
*/ (x2)
/**
* This will automatically inject sources to the media element. The sources will be
* appended as children to the media element as `<source>` elements.
*/ (x3)
// Merge sources into an array
// Clear the sources (x6)
// Add new sources (x4)
// Finally, load the new sources. (x4)
/**
* Apply composable state to the element, also when element is changed
*/ (x3)
/**
* Load Tracks
*/ (x3)
/**
* The MediaAPI provides an API for adding text tracks, but they don't currently
* have an API for removing text tracks, so instead we will just create and remove
* the tracks manually using the HTML api.
*/ (x6)
/**
* This will allow us to update the current time from the timeupdate event
* without setting the medias current position, but if the user changes the
* current time via the ref, then the media will seek.
*
* If we did not use an ignorable watch, then the current time update from
* the timeupdate event would cause the media to stutter.
*/ (x2)
/**
* Using an ignorable watch so we can control the play state using a ref and not
* a function
*/ (x2)
/**
* The following listeners need to listen to a nested
* object on the target, so we will have to use a nested
* watch and manually remove the listeners
*/ (x2)
// Remove text track listeners (x3)
// Volume (x2)
// Tracks (x2)
// Picture in Picture (x2)
Code
export function useMediaControls(target: MaybeRef<HTMLMediaElement | null | undefined>, options: UseMediaControlsOptions = {}): UseMediaControlsReturn {
target = toRef(target)
options = {
...defaultOptions,
...options,
}
const {
document = defaultDocument,
} = options
const listenerOptions = { passive: true }
const currentTime = shallowRef(0)
const duration = shallowRef(0)
const seeking = shallowRef(false)
const volume = shallowRef(1)
const waiting = shallowRef(false)
const ended = shallowRef(false)
const playing = shallowRef(false)
const rate = shallowRef(1)
const stalled = shallowRef(false)
const buffered = shallowRef<[number, number][]>([])
const tracks = shallowRef<UseMediaTextTrack[]>([])
const selectedTrack = shallowRef<number>(-1)
const isPictureInPicture = shallowRef(false)
const muted = shallowRef(false)
const supportsPictureInPicture = Boolean(document && 'pictureInPictureEnabled' in document)
// Events
const sourceErrorEvent = createEventHook<Event>()
const playbackErrorEvent = createEventHook<Event>()
/**
* Disables the specified track. If no track is specified then
* all tracks will be disabled
*
* @param track The id of the track to disable
*/
const disableTrack = (track?: number | UseMediaTextTrack) => {
usingElRef<HTMLMediaElement>(target, (el) => {
if (track) {
const id = typeof track === 'number' ? track : track.id
el.textTracks[id].mode = 'disabled'
}
else {
for (let i = 0; i < el.textTracks.length; ++i)
el.textTracks[i].mode = 'disabled'
}
selectedTrack.value = -1
})
}
/**
* Enables the specified track and disables the
* other tracks unless otherwise specified
*
* @param track The track of the id of the track to enable
* @param disableTracks Disable all other tracks
*/
const enableTrack = (track: number | UseMediaTextTrack, disableTracks = true) => {
usingElRef<HTMLMediaElement>(target, (el) => {
const id = typeof track === 'number' ? track : track.id
if (disableTracks)
disableTrack()
el.textTracks[id].mode = 'showing'
selectedTrack.value = id
})
}
/**
* Toggle picture in picture mode for the player.
*/
const togglePictureInPicture = () => {
return new Promise<PictureInPictureWindow | void>((resolve, reject) => {
usingElRef<HTMLVideoElement>(target, async (el) => {
if (supportsPictureInPicture) {
if (!isPictureInPicture.value) {
el.requestPictureInPicture().then(resolve).catch(reject)
}
else {
document!.exitPictureInPicture().then(resolve).catch(reject)
}
}
})
})
}
/**
* This will automatically inject sources to the media element. The sources will be
* appended as children to the media element as `<source>` elements.
*/
watchEffect(() => {
if (!document)
return
const el = toValue(target)
if (!el)
return
const src = toValue(options.src)
let sources: UseMediaSource[] = []
if (!src)
return
// Merge sources into an array
if (typeof src === 'string')
sources = [{ src }]
else if (Array.isArray(src))
sources = src
else if (isObject(src))
sources = [src]
// Clear the sources
el.querySelectorAll('source').forEach((e) => {
e.remove()
})
// Add new sources
sources.forEach(({ src, type, media }) => {
const source = document.createElement('source')
source.setAttribute('src', src)
source.setAttribute('type', type || '')
source.setAttribute('media', media || '')
useEventListener(source, 'error', sourceErrorEvent.trigger, listenerOptions)
el.appendChild(source)
})
// Finally, load the new sources.
el.load()
})
/**
* Apply composable state to the element, also when element is changed
*/
watch([target, volume], () => {
const el = toValue(target)
if (!el)
return
el.volume = volume.value
})
watch([target, muted], () => {
const el = toValue(target)
if (!el)
return
el.muted = muted.value
})
watch([target, rate], () => {
const el = toValue(target)
if (!el)
return
el.playbackRate = rate.value
})
/**
* Load Tracks
*/
watchEffect(() => {
if (!document)
return
const textTracks = toValue(options.tracks)
const el = toValue(target)
if (!textTracks || !textTracks.length || !el)
return
/**
* The MediaAPI provides an API for adding text tracks, but they don't currently
* have an API for removing text tracks, so instead we will just create and remove
* the tracks manually using the HTML api.
*/
el.querySelectorAll('track').forEach(e => e.remove())
textTracks.forEach(({ default: isDefault, kind, label, src, srcLang }, i) => {
const track = document.createElement('track')
track.default = isDefault || false
track.kind = kind
track.label = label
track.src = src
track.srclang = srcLang
if (track.default)
selectedTrack.value = i
el.appendChild(track)
})
})
/**
* This will allow us to update the current time from the timeupdate event
* without setting the medias current position, but if the user changes the
* current time via the ref, then the media will seek.
*
* If we did not use an ignorable watch, then the current time update from
* the timeupdate event would cause the media to stutter.
*/
const { ignoreUpdates: ignoreCurrentTimeUpdates } = watchIgnorable(currentTime, (time) => {
const el = toValue(target)
if (!el)
return
el.currentTime = time
})
/**
* Using an ignorable watch so we can control the play state using a ref and not
* a function
*/
const { ignoreUpdates: ignorePlayingUpdates } = watchIgnorable(playing, (isPlaying) => {
const el = toValue(target)
if (!el)
return
if (isPlaying) {
el.play().catch((e) => {
playbackErrorEvent.trigger(e)
throw e
})
}
else {
el.pause()
}
})
useEventListener(
target,
'timeupdate',
() => ignoreCurrentTimeUpdates(() => currentTime.value = (toValue(target))!.currentTime),
listenerOptions,
)
useEventListener(
target,
'durationchange',
() => duration.value = (toValue(target))!.duration,
listenerOptions,
)
useEventListener(
target,
'progress',
() => buffered.value = timeRangeToArray((toValue(target))!.buffered),
listenerOptions,
)
useEventListener(
target,
'seeking',
() => seeking.value = true,
listenerOptions,
)
useEventListener(
target,
'seeked',
() => seeking.value = false,
listenerOptions,
)
useEventListener(
target,
['waiting', 'loadstart'],
() => {
waiting.value = true
ignorePlayingUpdates(() => playing.value = false)
},
listenerOptions,
)
useEventListener(
target,
'loadeddata',
() => waiting.value = false,
listenerOptions,
)
useEventListener(
target,
'playing',
() => {
waiting.value = false
ended.value = false
ignorePlayingUpdates(() => playing.value = true)
},
listenerOptions,
)
useEventListener(
target,
'ratechange',
() => rate.value = (toValue(target))!.playbackRate,
listenerOptions,
)
useEventListener(
target,
'stalled',
() => stalled.value = true,
listenerOptions,
)
useEventListener(
target,
'ended',
() => ended.value = true,
listenerOptions,
)
useEventListener(
target,
'pause',
() => ignorePlayingUpdates(() => playing.value = false),
listenerOptions,
)
useEventListener(
target,
'play',
() => ignorePlayingUpdates(() => playing.value = true),
listenerOptions,
)
useEventListener(
target,
'enterpictureinpicture',
() => isPictureInPicture.value = true,
listenerOptions,
)
useEventListener(
target,
'leavepictureinpicture',
() => isPictureInPicture.value = false,
listenerOptions,
)
useEventListener(
target,
'volumechange',
() => {
const el = toValue(target)
if (!el)
return
volume.value = el.volume
muted.value = el.muted
},
listenerOptions,
)
/**
* The following listeners need to listen to a nested
* object on the target, so we will have to use a nested
* watch and manually remove the listeners
*/
const listeners: Fn[] = []
const stop = watch([target], () => {
const el = toValue(target)
if (!el)
return
stop()
listeners[0] = useEventListener(el.textTracks, 'addtrack', () => tracks.value = tracksToArray(el.textTracks), listenerOptions)
listeners[1] = useEventListener(el.textTracks, 'removetrack', () => tracks.value = tracksToArray(el.textTracks), listenerOptions)
listeners[2] = useEventListener(el.textTracks, 'change', () => tracks.value = tracksToArray(el.textTracks), listenerOptions)
})
// Remove text track listeners
tryOnScopeDispose(() => listeners.forEach(listener => listener()))
return {
currentTime,
duration,
waiting,
seeking,
ended,
stalled,
buffered,
playing,
rate,
// Volume
volume,
muted,
// Tracks
tracks,
selectedTrack,
enableTrack,
disableTrack,
// Picture in Picture
supportsPictureInPicture,
togglePictureInPicture,
isPictureInPicture,
// Events
onSourceError: sourceErrorEvent.on,
onPlaybackError: playbackErrorEvent.on,
}
}
usingElRef(source: MaybeRefOrGetter<any>, cb: (el: T) => void): void¶
Automatically check if the ref exists and if it does run the cb fn
Calls:
toValue (from vue)cb
Code
timeRangeToArray(timeRanges: TimeRanges): [number, number][]¶
Converts a TimeRange object to an array
Calls:
timeRanges.starttimeRanges.end
Code
tracksToArray(tracks: TextTrackList): UseMediaTextTrack[]¶
Converts a TextTrackList object to an array of UseMediaTextTrack
Calls:
Array.from(tracks) .map
Code
Internal helpers¶
Declared inside another function in this file.
disableTrack(track: number | UseMediaTextTrack): void¶
Parameters:
tracknumber | UseMediaTextTrack
Returns: void
Calls:
usingElRef
Code
(track?: number | UseMediaTextTrack) => {
usingElRef<HTMLMediaElement>(target, (el) => {
if (track) {
const id = typeof track === 'number' ? track : track.id
el.textTracks[id].mode = 'disabled'
}
else {
for (let i = 0; i < el.textTracks.length; ++i)
el.textTracks[i].mode = 'disabled'
}
selectedTrack.value = -1
})
}
enableTrack(track: number | UseMediaTextTrack, disableTracks: boolean): void¶
Parameters:
tracknumber | UseMediaTextTrackdisableTracksboolean
Returns: void
Calls:
usingElRefdisableTrack
Code
togglePictureInPicture(): Promise<void | PictureInPictureWindow>¶
Returns: Promise<void | PictureInPictureWindow>
Calls:
usingElRefel.requestPictureInPicture().then(resolve).catchdocument!.exitPictureInPicture().then(resolve).catch
Code
() => {
return new Promise<PictureInPictureWindow | void>((resolve, reject) => {
usingElRef<HTMLVideoElement>(target, async (el) => {
if (supportsPictureInPicture) {
if (!isPictureInPicture.value) {
el.requestPictureInPicture().then(resolve).catch(reject)
}
else {
document!.exitPictureInPicture().then(resolve).catch(reject)
}
}
})
})
}
Interfaces¶
UseMediaSource¶
Interface Code
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
src |
string |
✗ | not shown |
type |
string |
✓ | not shown |
media |
string |
✓ | not shown |
UseMediaTextTrackSource¶
Interface Code
export interface UseMediaTextTrackSource {
/**
* Indicates that the track should be enabled unless the user's preferences indicate
* that another track is more appropriate
*/
default?: boolean
/**
* How the text track is meant to be used. If omitted the default kind is subtitles.
*/
kind: TextTrackKind
/**
* A user-readable title of the text track which is used by the browser
* when listing available text tracks.
*/
label: string
/**
* Address of the track (.vtt file). Must be a valid URL. This attribute
* must be specified and its URL value must have the same origin as the document
*/
src: string
/**
* Language of the track text data. It must be a valid BCP 47 language tag.
* If the kind attribute is set to subtitles, then srclang must be defined.
*/
srcLang: string
}
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
default |
boolean |
✓ | not shown |
kind |
TextTrackKind |
✗ | not shown |
label |
string |
✗ | not shown |
src |
string |
✗ | not shown |
srcLang |
string |
✗ | not shown |
UseMediaControlsOptions¶
Interface Code
interface UseMediaControlsOptions extends ConfigurableDocument {
/**
* The source for the media, may either be a string, a `UseMediaSource` object, or a list
* of `UseMediaSource` objects.
*/
src?: MaybeRefOrGetter<string | UseMediaSource | UseMediaSource[]>
/**
* A list of text tracks for the media
*/
tracks?: MaybeRefOrGetter<UseMediaTextTrackSource[]>
}
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
src |
MaybeRefOrGetter<string \| UseMediaSource \| UseMediaSource[]> |
✓ | not shown |
tracks |
MaybeRefOrGetter<UseMediaTextTrackSource[]> |
✓ | not shown |
UseMediaTextTrack¶
Interface Code
export interface UseMediaTextTrack {
/**
* The index of the text track
*/
id: number
/**
* The text track label
*/
label: string
/**
* Language of the track text data. It must be a valid BCP 47 language tag.
* If the kind attribute is set to subtitles, then srclang must be defined.
*/
language: string
/**
* Specifies the display mode of the text track, either `disabled`,
* `hidden`, or `showing`
*/
mode: TextTrackMode
/**
* How the text track is meant to be used. If omitted the default kind is subtitles.
*/
kind: TextTrackKind
/**
* Indicates the track's in-band metadata track dispatch type.
*/
inBandMetadataTrackDispatchType: string
/**
* A list of text track cues
*/
cues: TextTrackCueList | null
/**
* A list of active text track cues
*/
activeCues: TextTrackCueList | null
}
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
id |
number |
✗ | not shown |
label |
string |
✗ | not shown |
language |
string |
✗ | not shown |
mode |
TextTrackMode |
✗ | not shown |
kind |
TextTrackKind |
✗ | not shown |
inBandMetadataTrackDispatchType |
string |
✗ | not shown |
cues |
TextTrackCueList \| null |
✗ | not shown |
activeCues |
TextTrackCueList \| null |
✗ | not shown |
UseMediaControlsReturn¶
Interface Code
export interface UseMediaControlsReturn {
currentTime: ShallowRef<number>
duration: ShallowRef<number>
waiting: ShallowRef<boolean>
seeking: ShallowRef<boolean>
ended: ShallowRef<boolean>
stalled: ShallowRef<boolean>
buffered: ShallowRef<[number, number][]>
playing: ShallowRef<boolean>
rate: ShallowRef<number>
// Volume
volume: ShallowRef<number>
muted: ShallowRef<boolean>
// Tracks
tracks: ShallowRef<UseMediaTextTrack[]>
selectedTrack: ShallowRef<number>
enableTrack: (track: number | UseMediaTextTrack, disableTracks?: boolean) => void
disableTrack: (track?: number | UseMediaTextTrack) => void
// Picture in Picture
supportsPictureInPicture: boolean
togglePictureInPicture: () => Promise<PictureInPictureWindow | void>
isPictureInPicture: ShallowRef<boolean>
// Events
onSourceError: EventHookOn<Event>
onPlaybackError: EventHookOn<Event>
}
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
currentTime |
ShallowRef<number> |
✗ | not shown |
duration |
ShallowRef<number> |
✗ | not shown |
waiting |
ShallowRef<boolean> |
✗ | not shown |
seeking |
ShallowRef<boolean> |
✗ | not shown |
ended |
ShallowRef<boolean> |
✗ | not shown |
stalled |
ShallowRef<boolean> |
✗ | not shown |
buffered |
ShallowRef<[number, number][]> |
✗ | not shown |
playing |
ShallowRef<boolean> |
✗ | not shown |
rate |
ShallowRef<number> |
✗ | not shown |
volume |
ShallowRef<number> |
✗ | not shown |
muted |
ShallowRef<boolean> |
✗ | not shown |
tracks |
ShallowRef<UseMediaTextTrack[]> |
✗ | not shown |
selectedTrack |
ShallowRef<number> |
✗ | not shown |
enableTrack |
(track: number \| UseMediaTextTrack, disableTracks?: boolean) => void |
✗ | not shown |
disableTrack |
(track?: number \| UseMediaTextTrack) => void |
✗ | not shown |
supportsPictureInPicture |
boolean |
✗ | not shown |
togglePictureInPicture |
() => Promise<PictureInPictureWindow \| void> |
✗ | not shown |
isPictureInPicture |
ShallowRef<boolean> |
✗ | not shown |
onSourceError |
EventHookOn<Event> |
✗ | not shown |
onPlaybackError |
EventHookOn<Event> |
✗ | not shown |
Generated by Syntax Scribe