目录

如何在Hugo博客中使用终端播放器Asciinema-Player

Asciinema-Player是一款著名的的终端录制播放器,可用于播放asciinema录制的播放文件,经常被用来进行终端操作演示。这里也将其引入到了当前Hugo博客中使用,下面我将讲讲引入的过程。

1.效果展示

Talk is cheap, show the result ~

这里直接借用官方demo进行展示。

2.需求分析

为什么需要在Hugo里面使用终端播放器Asciinema-Player呢?

作为技术人员,在写博客时总是需要进行一些终端操作演示,而演示方式无非以下几种:

方式 优点 缺点
视频 能配音配特效 网站流量消耗大,不能复制文本
动图 文件相对小些 不能控制进度,不能复制文本
代码 占用流量小,能复制 不能控制进度,也不太好展示效果

而终端录制播放器Asciinema-Player则兼容体积小、能控制、能复制于一体并能完美复现终端操作场景,非常适合用于在博客中进行一些终端操作演示。

3.解决方案

首先,在网上找下前人在Hugo博客里扩展嵌入asciinema-player的方法,比如 Embedding asciinema cast in your Hugo site

shortcode

可以找到里面asciinema-player的shortcode代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<p>
    <asciinema-player
        src="/casts/{{ with .Get "key" }}{{ . }}{{ end }}.cast"
        cols="{{ if .Get "cols" }}{{ .Get "cols" }}{{ else }}640{{ end }}"
        rows="{{ if .Get "rows" }}{{ .Get "rows" }}{{ else }}10{{ end }}"
        {{ if .Get "autoplay" }}autoplay="{{ .Get "autoplay" }}"{{ end }}
        {{ if .Get "preload" }}preload="{{ .Get "preload" }}"{{ end }}
        {{ if .Get "loop" }}loop="{{ .Get "loop" }}"{{ end }}
        start-at="{{ if .Get "start-at" }}{{ .Get "start-at" }}{{ else }}0{{ end }}"
        speed="{{ if .Get "speed" }}{{ .Get "speed" }}{{ else }}1{{ end }}"
        {{ if .Get "idle-time-limit" }}idle-time-limit="{{ .Get "idle-time-limit" }}"{{ end }}
        {{ if .Get "poster" }}poster="{{ .Get "poster" }}"{{ end }}
        {{ if .Get "font-size" }}font-size="{{ .Get "font-size" }}"{{ end }}
        {{ if .Get "theme" }}theme="{{ .Get "theme" }}"{{ end }}
        {{ if .Get "title" }}title="{{ .Get "title" }}"{{ end }}
        {{ if .Get "author" }}author="{{ .Get "author" }}"{{ end }}
        {{ if .Get "author-url" }}author-url="{{ .Get "author-url" }}"{{ end }}
        {{ if .Get "author-img-url" }}author-img-url="{{ .Get "author-img-url" }}"{{ end }}
    ></asciinema-player>
</p>

细细口味了下这段代码,结合自己需求进行了修改:

Talk is cheap, show me the code ~
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# themes/iLoveIt/layouts/shortcodes/asciinema.html
<p>
    <asciinema-player
        {{- with .Get "src" }} src="{{ . }}" {{ end -}}
        {{- with .Get "key" }} src="/casts/{{ . }}.cast"{{ end -}}
        cols="{{ if .Get "cols" }}{{ .Get "cols" }}{{ else }}640{{ end }}"
        rows="{{ if .Get "rows" }}{{ .Get "rows" }}{{ else }}10{{ end }}"
        {{- with .Get "autoplay" }}autoplay="{{ . }}"{{ end -}}
        {{- with .Get "preload" }}preload="{{ . }}"{{ end -}}
        {{- with .Get "loop" }}loop="{{ . }}"{{ end -}}
        start-at="{{ if .Get "start-at" }}{{ .Get "start-at" }}{{ else }}0{{ end }}"
        speed="{{ if .Get "speed" }}{{ .Get "speed" }}{{ else }}1{{ end }}"
        {{- with .Get "idle-time-limit" }}idle-time-limit="{{ . }}"{{ end -}}
        {{- with .Get "poster" }} poster="{{ . | safeURL }}"{{ end -}}
        {{- with .Get "font-size" }}font-size="{{ . }}"{{ end -}}
        {{- with .Get "theme" }}theme="{{ . }}"{{ end -}}
        {{- with .Get "title" }}title="{{ . }}"{{ end -}}
        {{- with .Get "author" }}author="{{ . }}"{{ end -}}
        {{- with .Get "author-url" }}author-url="{{ . }}"{{ end }}
        {{- with .Get "author-img-url" }}author-img-url="{{ . }}"{{ end -}}
        fit="{{ if .Get "fit" }}{{ .Get "fit" }}{{ else }}width{{ end }}"
    ></asciinema-player>
    {{- .Page.Scratch.SetInMap "this" "asciinema" true -}}
</p>

注意看高亮部分,主要修改了以下几点:

修改点
  1. if .Get 形式代码过于累赘,这里把不需要取默认值的语句统统改成了with .Get形式;
  2. 只有固定的key方法从本站获取.cast录制文件,这里扩展了src以便从站外获取录制文件地址;
  3. poster这里会得到一个奇怪的数据#ZgotmplZ,它是一个安全防护的默认数据,见官方说明,会导致设置指定时间封面无效,解决起来也简单,加上| safeURL管道方法就可解决;

js、css

参考方案里动态引入jscss,用到的该播放器的文章里需设置asciinematrue

1
2
3
{{ if .Params.asciinema }}
    <script src="{{ .Site.BaseURL }}js/asciinema-player.js"></script>
{{ end }}
1
2
3
4
5
6
7
8
---
title: Kubernetes Backup - ARK
description: Kubernetes backup process using ark
asciinema: true
tags:
  - kubernetes
  - backup
---

可以看到上面是通过在文章里加asciinema参数实现的jscss资源动态加载,其实可以参考其它如mermaid的接入方式,直接在渲染时置标记就好:

122
123
124
125
126
127
128
129
# themes/iLoveIt/layouts/partials/assets.html
{{- /* asciinema */ -}}
{{- if (.Scratch.Get "this").asciinema | or $params.draft -}}
    {{- $source := "lib/asciinema/asciinema-player.min.css" -}}
    {{- dict "Source" $source "Fingerprint" $fingerprint | dict "Scratch" .Scratch "Data" | partial "scratch/style.html" -}}
    {{- $source := "lib/asciinema/asciinema-player.min.js" -}}
    {{- dict "Source" $source "Fingerprint" $fingerprint | dict "Scratch" .Scratch "Data" | partial "scratch/script.html" -}}
{{- end -}}

其中$params.draft是为了开发模式下也能设置草稿参数进行预览:

53
54
55
56
57
58
59
60
# themes/iLoveIt/assets/data/cdn/jsdelivr.yml
  gitalkJS: gitalk@1.7.2/dist/gitalk.min.js
  # valine@1.5.0 https://valine.js.org/
  valineJS: valine@1.5.0/dist/Valine.min.js
  asciinemaJS: asciinema-player@3.0.1/dist/index.min.js
  # cookieconsent@3.1.1 https://github.com/osano/cookieconsent
  cookieconsentCSS: cookieconsent@3.1.1/build/cookieconsent.min.css
  cookieconsentJS: cookieconsent@3.1.1/build/cookieconsent.min.js

调整下css样式,让播放器能够在超出显示区域时滚动过去:

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/* themes/iLoveIt/assets/lib/asciinema/asciinema-player.min.css */
.asciinema-terminal {
  box-sizing: content-box;
  -moz-box-sizing: content-box;
  -webkit-box-sizing: content-box;
  padding: 0px;
  margin: 0px;
  display: block;
  white-space: pre;
  word-wrap: normal;
  word-break: normal;
  border-radius: 5px;
  border-style: solid;
  cursor: pointer;
  border-width: 0.3em;
  font-family: Consolas, Menlo, 'Bitstream Vera Sans Mono', monospace, 'Powerline Symbols', 'Terminal Glyphs';
  line-height: 1.3333333333em;
  width: auto;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
}
.asciinema-terminal .line {
  letter-spacing: normal;
  overflow: hidden;
  height: 1.3333333333em;
}
.asciinema-terminal .line span {
  padding: 0;
  display: inline-block;
  height: 1.3333333333em;
}
.asciinema-terminal .line {
  display: block;
  width: 4000px;
}

font

可以注意到font-family额外加了Terminal Glyphs,这也可参考了 Custom Fonts in Asciinema。 主要是为了不至于出现下文中乱码的问题。正常如下:

4.使用方法

asciinema-player

asciinema-player即播放器,在博客中使用只需简单在文章中加入以下代码:

1

其中,colsrows分别为行列,preload则是要不要预加载,poster则是封面,有data模式也有npt模式,npt即按时间截取封面,上面的就是截取55秒时的终端作为封面。 另外还有speed:播放速度,autoplay:自动播放等参数,具体可参考asciinema-player设置

呈现效果如下(借用了helix-editor的演示,文件大些可能加载慢些):

点击播放按钮播放,可拖动进度条或者使用方向键控制播放进度。

asciinema

上述终端播放所使用的cast文件,都是使用asciinema在终端录制的。

安装
1
brew install asciinema
使用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
➜  ~ asciinema --help
usage: asciinema [-h] [--version] {rec,play,cat,upload,auth} ...

Record and share your terminal sessions, the right way.

positional arguments:
  {rec,play,cat,upload,auth}
    rec                 Record terminal session
    play                Replay terminal session
    cat                 Print full output of terminal session
    upload              Upload locally saved terminal session to asciinema.org
    auth                Manage recordings on asciinema.org account

options:
  -h, --help            show this help message and exit
  --version             show program's version number and exit

example usage:
  Record terminal and upload it to asciinema.org:
    asciinema rec
  Record terminal to local file:
    asciinema rec demo.cast
  Record terminal and upload it to asciinema.org, specifying title:
    asciinema rec -t "My git tutorial"
  Record terminal to local file, limiting idle time to max 2.5 sec:
    asciinema rec -i 2.5 demo.cast
  Replay terminal recording from local file:
    asciinema play demo.cast
  Replay terminal recording hosted on asciinema.org:
    asciinema play https://asciinema.org/a/difqlgx86ym6emrmd8u62yqu8
  Print full output of recorded session:
    asciinema cat demo.cast

For help on a specific command run:
  asciinema <command> -h

另外如果想将其转化为gif动图,也可以使用asciicast2gif

5.总结

通过以上实践,虽然过程有些曲折,但最终还是顺利地将asciinema-player终端播放器引入到了hugo中使用,后续发布的博客也将经常见到其身影。