📄 index.ts
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 7 |
📦 Imports | 16 |
📊 Variables & Constants | 8 |
⚡ Async/Await Patterns | 2 |
🟢 Vue Composition API | 4 |
📐 Interfaces | 4 |
📑 Type Aliases | 1 |
📚 Table of Contents¶
- Imports
- Variables & Constants
- Async/Await Patterns
- Vue Composition API
- Functions
- Interfaces
- Type Aliases
🛠️ File Location:¶
📂 packages/core/useMediaControls/index.ts
📦 Imports¶
Name | Source |
---|---|
Fn |
@vueuse/shared |
MaybeRef |
vue |
MaybeRefOrGetter |
vue |
ConfigurableDocument |
../_configurable |
createEventHook |
@vueuse/shared |
isObject |
@vueuse/shared |
toRef |
@vueuse/shared |
tryOnScopeDispose |
@vueuse/shared |
watchIgnorable |
@vueuse/shared |
deepRef |
vue |
shallowRef |
vue |
toValue |
vue |
watch |
vue |
watchEffect |
vue |
defaultDocument |
../_configurable |
useEventListener |
../useEventListener |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
ranges |
[number, number][] |
let/var | [] |
✗ |
defaultOptions |
UseMediaControlsOptions |
const | `{ | |
src: '', | ||||
tracks: [], | ||||
}` | ✗ | |||
listenerOptions |
{ passive: boolean; } |
const | { passive: true } |
✗ |
supportsPictureInPicture |
boolean |
const | document && 'pictureInPictureEnabled' in document |
✗ |
id |
number |
const | typeof track === 'number' ? track : track.id |
✗ |
id |
number |
const | typeof track === 'number' ? track : track.id |
✗ |
sources |
UseMediaSource[] |
let/var | [] |
✗ |
listeners |
Fn[] |
const | [] |
✗ |
Async/Await Patterns¶
Type | Function | Await Expressions | Promise Chains |
---|---|---|---|
promise-chain | useMediaControls |
none | new Promise(...), (el as any).requestPictureInPicture().then(resolve).catch, (el as any).requestPictureInPicture().then, (document as any).exitPictureInPicture().then(resolve).catch, (document as any).exitPictureInPicture().then, el.play().catch |
promise-chain | togglePictureInPicture |
none | new Promise(...), (el as any).requestPictureInPicture().then(resolve).catch, (el as any).requestPictureInPicture().then, (document as any).exitPictureInPicture().then(resolve).catch, (document as any).exitPictureInPicture().then |
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¶
usingElRef(source: MaybeRefOrGetter<any>, cb: (el: T) => void): void
¶
Code
-
JSDoc:
-
Parameters:
source: MaybeRefOrGetter<any>
cb: (el: T) => void
- Return Type:
void
- Calls:
toValue (from vue)
cb
timeRangeToArray(timeRanges: TimeRanges): [number, number][]
¶
Code
-
JSDoc:
-
Parameters:
timeRanges: TimeRanges
- Return Type:
[number, number][]
- Calls:
timeRanges.start
timeRanges.end
tracksToArray(tracks: TextTrackList): UseMediaTextTrack[]
¶
Code
-
JSDoc:
-
Parameters:
tracks: TextTrackList
- Return Type:
UseMediaTextTrack[]
- Calls:
Array.from(tracks) .map
useMediaControls(target: MaybeRef<HTMLMediaElement | null | undefined>, options: UseMediaControlsOptions): { currentTime: any; duration: any; waiting: any; seeking: any; ended: any; stalled: any; buffered: any; playing: any; rate: any; volume: any; muted: any; tracks: any; selectedTrack: any; enableTrack: (track: number | UseMediaTextTrack, disableTracks?: boolean) => void; ... 5 more ...; onPlaybackError: any; }
¶
Code
export function useMediaControls(target: MaybeRef<HTMLMediaElement | null | undefined>, options: UseMediaControlsOptions = {}) {
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 = deepRef<[number, number][]>([])
const tracks = deepRef<UseMediaTextTrack[]>([])
const selectedTrack = shallowRef<number>(-1)
const isPictureInPicture = shallowRef(false)
const muted = shallowRef(false)
const supportsPictureInPicture = 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((resolve, reject) => {
usingElRef<HTMLVideoElement>(target, async (el) => {
if (supportsPictureInPicture) {
if (!isPictureInPicture.value) {
(el as any).requestPictureInPicture().then(resolve).catch(reject)
}
else {
(document as any).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,
}
}
- Parameters:
target: MaybeRef<HTMLMediaElement | null | undefined>
options: UseMediaControlsOptions
- Return Type:
{ currentTime: any; duration: any; waiting: any; seeking: any; ended: any; stalled: any; buffered: any; playing: any; rate: any; volume: any; muted: any; tracks: any; selectedTrack: any; enableTrack: (track: number | UseMediaTextTrack, disableTracks?: boolean) => void; ... 5 more ...; onPlaybackError: any; }
- Calls:
toRef (from @vueuse/shared)
shallowRef (from vue)
deepRef (from vue)
createEventHook (from @vueuse/shared)
usingElRef
disableTrack
(el as any).requestPictureInPicture().then(resolve).catch
(document as any).exitPictureInPicture().then(resolve).catch
watchEffect (from vue)
toValue (from vue)
Array.isArray
isObject (from @vueuse/shared)
el.querySelectorAll('source').forEach
e.remove
sources.forEach
document.createElement
source.setAttribute
useEventListener (from ../useEventListener)
el.appendChild
el.load
watch (from vue)
el.querySelectorAll('track').forEach
textTracks.forEach
watchIgnorable (from @vueuse/shared)
el.play().catch
playbackErrorEvent.trigger
el.pause
ignoreCurrentTimeUpdates
timeRangeToArray
ignorePlayingUpdates
stop
tracksToArray
tryOnScopeDispose (from @vueuse/shared)
listeners.forEach
listener
- 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)
disableTrack(track: number | UseMediaTextTrack): void
¶
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
})
}
- Parameters:
track: number | UseMediaTextTrack
- Return Type:
void
- Calls:
usingElRef
enableTrack(track: number | UseMediaTextTrack, disableTracks: boolean): void
¶
Code
- Parameters:
track: number | UseMediaTextTrack
disableTracks: boolean
- Return Type:
void
- Calls:
usingElRef
disableTrack
togglePictureInPicture(): Promise<unknown>
¶
Code
() => {
return new Promise((resolve, reject) => {
usingElRef<HTMLVideoElement>(target, async (el) => {
if (supportsPictureInPicture) {
if (!isPictureInPicture.value) {
(el as any).requestPictureInPicture().then(resolve).catch(reject)
}
else {
(document as any).exitPictureInPicture().then(resolve).catch(reject)
}
}
})
})
}
- Return Type:
Promise<unknown>
- Calls:
usingElRef
(el as any).requestPictureInPicture().then(resolve).catch
(document as any).exitPictureInPicture().then(resolve).catch
Interfaces¶
UseMediaSource
¶
Interface Code
Properties¶
Name | Type | Optional | Description |
---|---|---|---|
src |
string |
✗ | |
type |
string |
✓ | |
media |
string |
✓ |
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 |
✓ | |
kind |
TextTrackKind |
✗ | |
label |
string |
✗ | |
src |
string |
✗ | |
srcLang |
string |
✗ |
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[]> |
✓ | |
tracks |
MaybeRefOrGetter<UseMediaTextTrackSource[]> |
✓ |
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 |
✗ | |
label |
string |
✗ | |
language |
string |
✗ | |
mode |
TextTrackMode |
✗ | |
kind |
TextTrackKind |
✗ | |
inBandMetadataTrackDispatchType |
string |
✗ | |
cues |
TextTrackCueList | null |
✗ | |
activeCues |
TextTrackCueList | null |
✗ |