Skip to content

Commit 4468730

Browse files
feat: keep alive mode to handle vue transitions
1 parent fba95c6 commit 4468730

File tree

4 files changed

+104
-34
lines changed

4 files changed

+104
-34
lines changed

playground/App.vue

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
const index = ref(0)
1616
const id = ref('')
1717
18+
const destroy = shallowRef<() => void>()
1819
const player = shallowRef<Player>()
1920
const paused = ref(false)
2021
const muted = ref(true)
@@ -106,23 +107,31 @@
106107
</form>
107108
</div>
108109
<div>
109-
<FreecasterPlayer
110-
v-if="mounted"
111-
class="player"
112-
:video-id="ids[index]"
113-
controls
114-
autoplay
115-
subtitles-default-lang="fr"
116-
v-model="player"
117-
v-model:paused="paused"
118-
v-model:muted="muted"
119-
v-model:current-time="currentTime"
120-
v-model:fullscreen="fullscreen"
121-
v-model:volume="volume"
122-
v-model:ready-state="readyState"
123-
v-model:current-subtitles="currentSubtitles"
124-
v-model:subtitles="subtitles"
125-
/>
110+
<transition
111+
mode="out-in"
112+
@after-leave="destroy()"
113+
>
114+
<FreecasterPlayer
115+
v-if="mounted"
116+
ref="component"
117+
class="player"
118+
:video-id="ids[index]"
119+
controls
120+
autoplay
121+
subtitles-default-lang="fr"
122+
keep-alive
123+
v-model="player"
124+
v-model:paused="paused"
125+
v-model:muted="muted"
126+
v-model:current-time="currentTime"
127+
v-model:fullscreen="fullscreen"
128+
v-model:volume="volume"
129+
v-model:ready-state="readyState"
130+
v-model:current-subtitles="currentSubtitles"
131+
v-model:subtitles="subtitles"
132+
v-model:destroy="destroy"
133+
/>
134+
</transition>
126135
<div class="controls">
127136
<button @click="paused = !paused">
128137
{{ paused ? 'play' : 'pause' }}
@@ -259,6 +268,14 @@
259268
position: relative;
260269
margin: 0;
261270
display: block !important;
271+
272+
&.v-enter-from, &.v-leave-to {
273+
opacity: 0;
274+
}
275+
276+
&.v-enter-active, &.v-leave-active {
277+
transition: opacity .2s;
278+
}
262279
}
263280
264281
.controls {

src/components/FreecasterPlayer.vue

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import type { Player, PlayerOptions, PlayerEvents } from '../types/Player'
55
66
export interface FreecasterPlayerProps extends PlayerOptions {
7-
7+
keepAlive?: boolean
88
}
99
1010
export type FreecasterPlayerEmits = PlayerEvents
@@ -21,18 +21,7 @@
2121
const readyState = defineModel<number>('readyState', { default: 0 })
2222
const currentSubtitles = defineModel<TextTrack>('currentSubtitles')
2323
const subtitles = defineModel<TextTrack[]>('subtitles', { default: [] })
24-
25-
defineExpose({
26-
player,
27-
paused,
28-
muted,
29-
fullscreen,
30-
volume,
31-
currentTime,
32-
readyState,
33-
currentSubtitles,
34-
subtitles
35-
})
24+
const destroyModel = defineModel<() => void>('destroy')
3625
3726
const options = computed(() => ({
3827
videoId: props.videoId,
@@ -71,7 +60,8 @@
7160
7261
const attrs = useAttrs()
7362
74-
const { element, key, attributes } = usePlayer({
63+
const { element, key, attributes, destroy } = usePlayer({
64+
keepAlive: computed(() => props.keepAlive),
7565
options,
7666
player,
7767
paused,
@@ -84,6 +74,21 @@
8474
currentSubtitles,
8575
subtitles,
8676
})
77+
78+
destroyModel.value = destroy
79+
80+
defineExpose({
81+
player,
82+
paused,
83+
muted,
84+
fullscreen,
85+
volume,
86+
currentTime,
87+
readyState,
88+
currentSubtitles,
89+
subtitles,
90+
destroy
91+
})
8792
</script>
8893

8994
<template>

src/composables/player.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
shallowRef,
77
toRef,
88
isRef,
9+
unref,
910
type Ref,
1011
type MaybeRefOrGetter,
1112
type MaybeRef,
@@ -84,6 +85,15 @@ export interface UsePlayerParameters {
8485
*/
8586
element?: MaybeRef<HTMLElement | undefined>
8687

88+
/**
89+
* Wether to keep the player instance alive after the component is unmounted.
90+
* It can then be manually destroyed by calling
91+
* {@link UsePlayerContext.destroy}. This can be useful to keep the player
92+
* instance alive during vue leave transitions.
93+
* @default false
94+
*/
95+
keepAlive?: MaybeRef<boolean>
96+
8797
/**
8898
* Default event listener, called for all events
8999
* @default undefined
@@ -105,12 +115,46 @@ export interface UsePlayerParameters {
105115
}
106116

107117
export interface UsePlayerContext extends UsePlayerParameters {
118+
/**
119+
* Ref to the freecaster load function.
120+
*/
108121
load: Ref<((element: Element) => void) | undefined>
122+
123+
/**
124+
* Destroy the currently attached player instance.
125+
*/
126+
destroy: () => void
127+
128+
/**
129+
* Event listeners for the player.
130+
*/
109131
listeners: Required<UsePlayerParameters>['listeners']
132+
133+
/**
134+
* Player options provided in {@link UsePlayerParameters.options}.
135+
*/
110136
options: Ref<PlayerOptions | undefined>
137+
138+
/**
139+
* Ref to the player instance.
140+
*/
111141
player: Ref<Player | undefined>
142+
143+
/**
144+
* Ref to the element where the player is attached. Can be passed to the
145+
* special ref prop of an element to attach the player to.
146+
*/
112147
element: Ref<HTMLElement | undefined>
148+
149+
/**
150+
* Ref to the player attributes that can be used to bind to the player. Can be
151+
* passed the v-bind directive of an element to attach the player to.
152+
*/
113153
attributes: Ref<PlayerAttributes>
154+
155+
/**
156+
* Ref to a key that is updated every time the player loads a new video.
157+
*/
114158
key: Ref<number>
115159
}
116160

@@ -124,6 +168,7 @@ export function usePlayer(parameters: UsePlayerParameters = {}): UsePlayerContex
124168

125169
const context: UsePlayerContext = {
126170
...parameters,
171+
destroy: () => remove(context),
127172
load: shallowRef(),
128173
listeners,
129174
key: shallowRef(0),
@@ -281,6 +326,10 @@ function useLifecycle({
281326
options,
282327
...context
283328
}: UsePlayerContext): void {
329+
if (context.player.value) {
330+
context.player.value = undefined
331+
}
332+
284333
let loadInterval: ReturnType<typeof setInterval>
285334

286335
const initialize = (value: Player) => {
@@ -321,7 +370,7 @@ function useLifecycle({
321370
clearInterval(loadInterval)
322371
const index = (window._fcpr ||= []).indexOf(initialize)
323372
index === -1 || _fcpr.splice(index, 1)
324-
remove(context)
373+
unref(context.keepAlive) || context.destroy()
325374
})
326375
}
327376

src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ export * from './composables/player'
55
export {
66
default as FreecasterPlayer,
77
type FreecasterPlayerProps,
8-
type FreecasterPlayerEmits,
9-
type FreecasterPlayerSlots
8+
type FreecasterPlayerEmits
109
} from './components/FreecasterPlayer.vue'
1110

1211
export {

0 commit comments

Comments
 (0)