Skip to content

Commit

Permalink
feat: enhance SlidevVideo component (#1435)
Browse files Browse the repository at this point in the history
  • Loading branch information
KermanX committed Apr 10, 2024
1 parent af0bb47 commit 1abcd35
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 84 deletions.
28 changes: 20 additions & 8 deletions docs/builtin/components.md
@@ -1,3 +1,7 @@
---
outline: [2, 3]
---

# Components

## Built-in Components
Expand Down Expand Up @@ -290,17 +294,25 @@ Check [HTML video element's doc](https://developer.mozilla.org/docs/Web/HTML/Ele

Parameters:

- `autoPlay` (`boolean | 'once' | 'resume' | 'resumeOnce'`, default: `false`):
- `controls` (`boolean`, default: `false`): show the video controls
- `autoplay` (`boolean | 'once'`, default: `false`):
- `true` or `'once'`: start the video only once and does not restart it once ended or paused
- `false`: never automatically start the video (rely on html5 controls instead)
- `'resume'` resume the video when going back to its click turn
- `'resumeOnce'` only resume it if it hasn't ended
- `autoPause` (`'slide' | 'click'`, default: `undefined`):
- `'slide'`: pause the video on slide change
- `'click'`: pause on next click
- `autoReset` (`'slide' | 'click'`, default: `undefined`):
- `false`: never automatically start the video (rely on `controls` instead)
- `autoreset` (`'slide' | 'click'`, default: `undefined`):
- `'slide'`: go back to the start of the video when going back to the slide
- `'click'`: go back to the start of the video when going back to the component's click turn
- `poster` (`string | undefined`, default: `undefined`):
- The source of the image to print when the video is not playing.
- `printPoster` (`string | undefined`, default: `undefined`):
- The override for `poster` when printing.
- `timestamp` (`string | number`, default: `0`):
- The starting time of the video in seconds.
- `printTimestamp` (`string | number | 'last' | undefined`, default: `undefined`):
- The override for `timestamp` when printing.

::: warning
When exporting, the video may fail to load because Chromium does not support some video formats. In this case, you can specify the executable path of the browser. See [Chromium executable path](/guide/exporting.html#executable-path) for more information.
:::

### `Youtube`

Expand Down
28 changes: 14 additions & 14 deletions docs/guide/exporting.md
Expand Up @@ -4,16 +4,16 @@

### PDF

> Exporting to PDF or PNG relies on [Playwright](https://playwright.dev) for rendering. You will therefore need to install [`playwright-chromium`](https://playwright.dev/docs/installation#download-single-browser-binary) to use this feature.
> Exporting to PDF or PNG relies on [Playwright](https://playwright.dev) for rendering. You will therefore need to install [`playwright-chromium`](https://npmjs.com/package/playwright-chromium) to use this feature.
> If you are doing exporting in a CI environment, [the playwright CI guide](https://playwright.dev/docs/ci) can be helpful.
Install `playwright-chromium`
1. Install `playwright-chromium`:

```bash
$ npm i -D playwright-chromium
```

Now export your slides to PDF using the following command
2. Now export your slides to PDF using the following command:

```bash
$ slidev export
Expand All @@ -23,13 +23,13 @@ After a few seconds, your slides will be ready at `./slides-export.pdf`.

### PNGs and Markdown

When passing in the `--format png` option, Slidev will export PNG images for each slide instead of a PDF.
When passing in the `--format png` option, Slidev will export PNG images for each slide instead of a PDF:

```bash
$ slidev export --format png
```

You can also compile a markdown file composed of compiled png using `--format md`.
You can also compile a markdown file composed of compiled png using `--format md`:

```bash
$ slidev export --format md
Expand All @@ -47,15 +47,15 @@ $ slidev export --dark

> Available since v0.21
By default, Slidev exports one page per slide with clicks animations disabled. If you want to export slides with multiple steps into multiple pages, pass the `--with-clicks` option.
By default, Slidev exports one page per slide with clicks animations disabled. If you want to export slides with multiple steps into multiple pages, pass the `--with-clicks` option:

```bash
$ slidev export --with-clicks
```

### Slide range

You can also specify a range of slides to export with the `--range` option.
You can also specify a range of slides to export with the `--range` option:

```bash
$ slidev export --range 1,4-5,6
Expand All @@ -65,15 +65,15 @@ $ slidev export --range 1,4-5,6

> Available since v0.36.10
You can generate the PDF outline by passing the `--with-toc` option.
You can generate the PDF outline by passing the `--with-toc` option:

```bash
$ slidev export --with-toc
```

### Output filename

You can specify the output filename with the `--output` option.
You can specify the output filename with the `--output` option:

```bash
$ slidev export --output my-pdf-export
Expand All @@ -89,7 +89,7 @@ exportFilename: my-pdf-export

### Export a range of slides

By default, all slides in the presentation are exported. If you want to export a specific slide or a range of slides you can set the `--range` option and specify which slides you would like to export.
By default, all slides in the presentation are exported. If you want to export a specific slide or a range of slides you can set the `--range` option and specify which slides you would like to export:

```bash
$ slidev export --range 1,6-8,10
Expand All @@ -101,7 +101,7 @@ The example above would export slides 1,6,7,8 and 10.

### Multiple entries

You can also export multiple slides at once.
You can also export multiple slides at once:

```bash
$ slidev export slides1.md slides1.md
Expand All @@ -119,7 +119,7 @@ In this case, each input file will generate its own PDf file.

> Available since v0.36.8
Export only the presenter notes (the last comment block for each slide) into a text document in PDF.
Export only the presenter notes (the last comment block for each slide) into a text document in PDF:

```bash
$ slidev export-notes
Expand Down Expand Up @@ -153,7 +153,7 @@ docker exec -i slidev npx slidev export --timeout 2m --output slides.pdf

### Timeout

For big presentations you might want to increase the Playwright timeout with `--timeout`
For big presentations you might want to increase the Playwright timeout with `--timeout`:

```bash
$ slidev export --timeout 60000
Expand All @@ -169,7 +169,7 @@ $ slidev export --wait 10000

### Executable path

You can set the browser executable path for Playwright using `--executable-path`
Chromium may miss some features like codecs that are required to decode some videos. You can set the browser executable path for Playwright to your Chrome or Edge using `--executable-path`:

```bash
$ slidev export --executable-path [path_to_chromium]
Expand Down
116 changes: 54 additions & 62 deletions packages/client/builtin/SlidevVideo.vue
@@ -1,86 +1,78 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import { and } from '@vueuse/math'
import { useSlideContext } from '../context'
import { useNav } from '../composables/useNav'
const props = defineProps<{
autoPlay?: boolean | 'once' | 'resume' | 'resumeOnce'
autoPause?: 'slide' | 'click'
autoReset?: 'slide' | 'click'
autoplay?: boolean | 'once'
autoreset?: 'slide' | 'click'
poster?: string
printPoster?: string
timestamp?: string | number
printTimestamp?: string | number | 'last'
controls?: boolean
}>()
const printPoster = computed(() => props.printPoster ?? props.poster)
const printTimestamp = computed(() => props.printTimestamp ?? props.timestamp ?? 0)
const {
$slidev,
$clicksContext: clicks,
$renderContext: currentContext,
$route: route,
$clicksContext,
$renderContext,
$route,
} = useSlideContext()
const { isPrintMode } = useNav()
const noPlay = computed(() => isPrintMode.value || !['slide', 'presenter'].includes($renderContext.value))
const video = ref<HTMLMediaElement>()
const played = ref(false)
const ended = ref(false)
const matchRoute = computed(() => {
if (!video.value || currentContext?.value !== 'slide')
return false
return route && route.no === $slidev?.nav.currentSlideNo
})
const matchClick = computed(() => {
if (!video.value || currentContext?.value !== 'slide' || !clicks)
return false
return clicks.map.get(video.value)?.isShown?.value ?? true
})
const matchRouteAndClick = computed(() => matchRoute.value && matchClick.value)
watch(matchRouteAndClick, () => {
if (!video.value || currentContext?.value !== 'slide')
return
if (matchRouteAndClick.value) {
if (props.autoReset === 'click')
video.value.currentTime = 0
if (props.autoPlay && (!played.value || props.autoPlay === 'resume' || (props.autoPlay === 'resumeOnce' && !ended.value)))
video.value.play()
}
if ((props.autoPause === 'click' && !matchRouteAndClick.value) || (props.autoPause === 'slide' && !matchRoute.value))
video.value.pause()
})
watch(matchRoute, () => {
if (!video.value || currentContext?.value !== 'slide')
onMounted(() => {
if (noPlay.value)
return
if (matchRoute.value && props.autoReset === 'slide')
video.value.currentTime = 0
const timestamp = +(props.timestamp ?? 0)
video.value!.currentTime = timestamp
const matchRoute = computed(() => !!$route && $route.no === $slidev?.nav.currentSlideNo)
const matchClick = computed(() => !!video.value && ($clicksContext.map.get(video.value)?.isShown?.value ?? true))
const matchRouteAndClick = and(matchRoute, matchClick)
watch(matchRouteAndClick, () => {
if (matchRouteAndClick.value) {
if (props.autoplay === true || (props.autoplay === 'once' && !played.value))
video.value!.play()
}
else {
video.value!.pause()
if (props.autoreset === 'click' || (props.autoreset === 'slide' && !matchRoute.value))
video.value!.currentTime = timestamp
}
}, { immediate: true })
})
function onPlay() {
played.value = true
}
function onEnded() {
ended.value = true
function onLoadedMetadata(ev: Event) {
// The video may be loaded before component mounted
const element = ev.target as HTMLMediaElement
if (noPlay.value && (!printPoster.value || props.printTimestamp)) {
element.currentTime = printTimestamp.value === 'last'
? element.duration
: +printTimestamp.value
}
}
onMounted(() => {
if (!video.value || currentContext?.value !== 'slide')
return
video.value?.addEventListener('play', onPlay)
video.value?.addEventListener('ended', onEnded)
})
onUnmounted(() => {
if (!video.value || currentContext?.value !== 'slide')
return
video.value?.removeEventListener('play', onPlay)
video.value?.removeEventListener('ended', onEnded)
})
</script>

<template>
<video ref="video">
<video
ref="video"
:poster="noPlay ? printPoster : props.poster"
:controls="!noPlay && props.controls"
@play="played = true"
@loadedmetadata="onLoadedMetadata"
>
<slot />
</video>
</template>

0 comments on commit 1abcd35

Please sign in to comment.