Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/css/compiled/main.css

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions assets/css/components/media.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.content .plyr {
@apply hx:mx-auto hx:my-6 hx:rounded-md hx:overflow-hidden;
}
1 change: 1 addition & 0 deletions assets/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,4 @@ body {
@import "./components/badge.css";
@import "./components/toc.css";
@import "./components/archives.css";
@import "./components/media.css";
6 changes: 6 additions & 0 deletions assets/css/typography.css
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@
:where(img):not(:where([class~=not-prose],[class~=not-prose] *)) {
@apply hx:mx-auto hx:my-4 hx:rounded-md;
}
:where(audio):not(:where([class~=not-prose],[class~=not-prose] *)) {
@apply hx:mx-auto hx:rounded-md;
}
:where(video):not(:where([class~=not-prose],[class~=not-prose] *)) {
@apply hx:mx-auto hx:rounded-md;
}
:where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) {
figcaption {
@apply hx:text-sm hx:text-gray-500 hx:dark:text-gray-400 hx:mt-2 hx:block hx:text-center;
Expand Down
1 change: 1 addition & 0 deletions docs/content/docs/guide/shortcodes/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ Additional shortcodes provided by Hugo and Hextra:
{{< card link="others" title="Others" icon="view-grid" >}}
{{< card link="hextra" title="Hextra" icon="view-grid" >}}
{{< card link="asciinema" title="Asciinema Player" icon="terminal" >}}
{{< card link="media" title="Media Player" icon="play" >}}
{{< /cards >}}
111 changes: 111 additions & 0 deletions docs/content/docs/guide/shortcodes/media.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
---
title: "Media Player Component"
linktitle: "Media Player"
sidebar:
exclude: true
---

## Overview

The media shortcode allows you to embed media using the [Plyr](https://plyr.io/) player in your Hugo site. It supports HTML5 video, HTML5 audio, YouTube, and Vimeo with a polished, accessible player interface.

## Basic Usage

### Local Files

Place your media files in the `static/` directory:

```
your-site/
├── static/
│ └── media/
│ ├── demo.mp4
│ └── podcast.mp3
└── content/
└── my-page.md
```

Video:

```markdown
{{</* media src="media/demo.mp4" */>}}
```

Audio:

```markdown
{{</* media src="media/podcast.mp3" type="audio" */>}}
```

### YouTube

Embed a YouTube video using its video ID:

```markdown
{{</* media src="dQw4w9WgXcQ" provider="youtube" */>}}
```

Result:

{{< media src="dQw4w9WgXcQ" provider="youtube" >}}

### Vimeo

Embed a Vimeo video using its video ID:

```markdown
{{</* media src="76979871" provider="vimeo" */>}}
```

## Advanced Usage

Here's an example with all available parameters:

```markdown
{{</* media
src="media/demo.mp4"
autoplay="true"
controls="true"
crossorigin="anonymous"
loop="true"
muted="true"
playsinline="true"
preload="auto"
*/>}}
```

## Parameters

| Parameter | Type | Default | Description |
| ------------- | ------- | ---------- | ------------------------------------------------------------- |
| `src` | string | - | The source URL, path, or embed ID (required) |
| `autoplay` | boolean | `false` | Start playing automatically |
| `controls` | boolean | `true` | Show player controls |
| `crossorigin` | string | `""` | The CORS setting: `"anonymous"` or `"use-credentials"` |
| `loop` | boolean | `false` | Loop the media |
| `muted` | boolean | `false` | Mute the media |
| `playsinline` | boolean | `true` | Play inline on mobile instead of fullscreen |
| `preload` | string | `"auto"` | The preload behavior: `"none"`, `"metadata"`, or `"auto"` |
| `provider` | string | `""` | The media provider: `"youtube"`, `"vimeo"`, or omit for HTML5 |
| `type` | string | `"video"` | The media type: `"video"` or `"audio"` |

## Configuration

By default, Plyr assets are loaded from the official CDN. You can customize the asset source in your site configuration:

```yaml
params:
plyr:
base: "https://cdn.jsdelivr.net/npm/plyr@latest/dist" # Custom CDN base URL
css: "plyr.css" # Custom CSS file path
js: "plyr.polyfilled.js" # Custom JS file path
```

To use local assets, set `js` and/or `css` without `base`:

```yaml
params:
plyr:
css: "css/plyr.css" # Path relative to assets/
js: "js/plyr.polyfilled.js" # Path relative to assets/
```
1 change: 1 addition & 0 deletions docs/hugo_stats.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions layouts/_partials/scripts.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@
{{- if (.Store.Get "hasImageZoom") -}}
{{- partial "scripts/medium-zoom.html" . -}}
{{- end -}}

{{/* Plyr */}}
{{- if (.Store.Get "hasPlyr") -}}
{{- partial "scripts/plyr.html" . -}}
{{- end -}}
85 changes: 85 additions & 0 deletions layouts/_partials/scripts/plyr.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{{- /* Plyr */ -}}

{{- $plyrBase := "" -}}
{{- $useDefaultCdn := true -}}
{{- with site.Params.plyr.base -}}
{{- $plyrBase = . -}}
{{- $useDefaultCdn = false -}}
{{- end -}}

{{- $plyrJsAsset := "" -}}
{{- with site.Params.plyr.js -}}
{{- $plyrJsAsset = . -}}
{{- end -}}

{{- $plyrCssAsset := "" -}}
{{- with site.Params.plyr.css -}}
{{- $plyrCssAsset = . -}}
{{- end -}}

{{- /* If only js/css is set without base, use local asset loading */ -}}
{{- if and $useDefaultCdn (or (ne $plyrJsAsset "") (ne $plyrCssAsset "")) -}}
{{- $useDefaultCdn = false -}}
{{- end -}}

{{- /* Set default CDN base if needed */ -}}
{{- if $useDefaultCdn -}}
{{- $plyrBase = "https://cdn.jsdelivr.net/npm/plyr@latest/dist" -}}
{{- end -}}

{{- $isRemoteBase := or (strings.HasPrefix $plyrBase "http://") (strings.HasPrefix $plyrBase "https://") -}}
{{- $minSuffix := cond hugo.IsProduction ".min" "" -}}

{{- /* CSS retrieval */ -}}
{{- if $isRemoteBase -}}
{{- $cssPath := cond (ne $plyrCssAsset "") $plyrCssAsset "plyr.css" -}}
{{- $plyrCssUrl := printf "%s/%s" $plyrBase $cssPath -}}
{{- with try (resources.GetRemote $plyrCssUrl) -}}
{{- with .Err -}}
{{- errorf "Could not retrieve Plyr css file from %s. Reason: %s." $plyrCssUrl . -}}
{{- else with .Value -}}
{{- with resources.Copy "css/plyr.css" . -}}
{{- $plyrCss := . | fingerprint -}}
<link rel="stylesheet" href="{{ $plyrCss.RelPermalink }}" integrity="{{ $plyrCss.Data.Integrity }}" crossorigin="anonymous" />
{{- end -}}
{{- end -}}
{{- end -}}
{{- else if $plyrCssAsset -}}
{{- with resources.Get $plyrCssAsset -}}
{{- $plyrCss := . | fingerprint -}}
<link rel="stylesheet" href="{{ $plyrCss.RelPermalink }}" integrity="{{ $plyrCss.Data.Integrity }}" crossorigin="anonymous" />
{{- else -}}
{{- errorf "Plyr css asset not found at %q" $plyrCssAsset -}}
{{- end -}}
{{- end -}}

{{- /* JS retrieval */ -}}
{{- if $isRemoteBase -}}
{{- $jsPath := cond (ne $plyrJsAsset "") $plyrJsAsset (printf "plyr.polyfilled%s.js" $minSuffix) -}}
{{- $plyrJsUrl := printf "%s/%s" $plyrBase $jsPath -}}
{{- with try (resources.GetRemote $plyrJsUrl) -}}
{{- with .Err -}}
{{- errorf "Could not retrieve Plyr js file from %s. Reason: %s." $plyrJsUrl . -}}
{{- else with .Value -}}
{{- with resources.Copy (printf "js/plyr.polyfilled%s.js" $minSuffix) . -}}
{{- $plyrJs := . | fingerprint -}}
<script defer src="{{ $plyrJs.RelPermalink }}" integrity="{{ $plyrJs.Data.Integrity }}" crossorigin="anonymous"></script>
{{- end -}}
{{- end -}}
{{- end -}}
{{- else if $plyrJsAsset -}}
{{- with resources.Get $plyrJsAsset -}}
{{- $plyrJs := . | fingerprint -}}
<script defer src="{{ $plyrJs.RelPermalink }}" integrity="{{ $plyrJs.Data.Integrity }}" crossorigin="anonymous"></script>
{{- else -}}
{{- errorf "Plyr js asset not found at %q" $plyrJsAsset -}}
{{- end -}}
{{- end -}}

<script>
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".content video, .content audio, .content [data-plyr-provider]").forEach((el) => {
new Plyr(el);
});
});
</script>
29 changes: 29 additions & 0 deletions layouts/_partials/shortcodes/media.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{{- /* Plyr partial: renders HTML5 audio, HTML5 video, Vimeo, or YouTube */ -}}
<div class="hx:mx-auto hx:my-6">
{{- if eq .type "audio" -}}
<audio
{{- if eq (printf "%v" .autoplay) "true" }} autoplay{{ end -}}
{{- if eq (printf "%v" .controls) "true" }} controls{{ end -}}
{{- with .crossorigin }} crossorigin="{{ . }}"{{ end -}}
{{- if eq (printf "%v" .loop) "true" }} loop{{ end -}}
{{- if eq (printf "%v" .muted) "true" }} muted{{ end -}}
{{- " " }}preload="{{ .preload }}">
<source src="{{ .src }}" />
</audio>
{{- else if eq .provider "vimeo" -}}
<div data-plyr-provider="vimeo" data-plyr-embed-id="{{ .src }}"></div>
{{- else if eq .provider "youtube" -}}
<div data-plyr-provider="youtube" data-plyr-embed-id="{{ .src }}"></div>
{{- else -}}
<video
{{- if eq (printf "%v" .autoplay) "true" }} autoplay{{ end -}}
{{- if eq (printf "%v" .controls) "true" }} controls{{ end -}}
{{- with .crossorigin }} crossorigin="{{ . }}"{{ end -}}
{{- if eq (printf "%v" .loop) "true" }} loop{{ end -}}
{{- if eq (printf "%v" .muted) "true" }} muted{{ end -}}
{{- if eq (printf "%v" .playsinline) "true" }} playsinline{{ end -}}
{{- " " }}preload="{{ .preload }}">
<source src="{{ .src }}" type="video/mp4" />
</video>
{{- end -}}
</div>
62 changes: 62 additions & 0 deletions layouts/_shortcodes/media.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{{- /*
A shortcode to embed media using the Plyr player.

Supports HTML5 video, HTML5 audio, YouTube, and Vimeo.

@param {string} src The source URL, path, or embed ID.
@param {string} [provider] The media provider: "youtube", "vimeo", or omit for HTML5.
@param {string} [type=video] The media type: "video" or "audio".
@param {bool} [autoplay=false] Whether to autoplay.
@param {bool} [controls=true] Whether to show player controls.
@param {string} [crossorigin] The CORS setting: "anonymous" or "use-credentials".
@param {bool} [loop=false] Whether to loop.
@param {bool} [muted=false] Whether to mute.
@param {bool} [playsinline=true] Whether to play inline on mobile.
@param {string} [preload=auto] The preload behavior: "none", "metadata", or "auto".

@example {{< media src="media/example.mp4" >}}
@example {{< media src="bTqVqk7FSmY" provider="youtube" >}}
@example {{< media src="76979871" provider="vimeo" >}}
@example {{< media src="media/example.mp3" type="audio" >}}
*/ -}}
{{- $src := .Get "src" | default (.Get 0) -}}
{{- $provider := .Get "provider" | default "" -}}
{{- $type := .Get "type" | default "video" -}}
{{- $autoplay := .Get "autoplay" | default false -}}
{{- $controls := .Get "controls" | default true -}}
{{- $crossorigin := .Get "crossorigin" | default "" -}}
{{- $loop := .Get "loop" | default false -}}
{{- $muted := .Get "muted" | default false -}}
{{- $playsinline := .Get "playsinline" | default true -}}
{{- $preload := .Get "preload" | default "auto" -}}
{{- if not $src -}}
{{- errorf "Media 'src' must be supplied" -}}
{{- end -}}

{{/* Resolve local file path for HTML5 media */}}
{{- if not $provider -}}
{{- $isLocal := not (urls.Parse $src).Scheme -}}
{{- if $isLocal -}}
{{- if hasPrefix $src "/" -}}
{{- $src = relURL (strings.TrimPrefix "/" $src) -}}
{{- else -}}
{{- $src = relURL $src -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/* Mark page as using Plyr */}}
{{- .Page.Store.Set "hasPlyr" true -}}

{{- partial "shortcodes/media" (dict
"src" $src
"provider" $provider
"type" $type
"autoplay" $autoplay
"controls" $controls
"crossorigin" $crossorigin
"loop" $loop
"muted" $muted
"playsinline" $playsinline
"preload" $preload
) -}}
Loading