├── .babelrc ├── .editorconfig ├── .gitignore ├── .npmignore ├── AUTHORS ├── LICENSE ├── README.md ├── common ├── constant.es └── util.es ├── docs └── README-zh_cn.md ├── gulpfile.babel.js ├── index.deprecated.es ├── index.es ├── lib ├── config.es ├── tag │ ├── base.es │ ├── player.es │ ├── playerList.es │ ├── playerLyric.es │ └── playerMeting.es └── view.es └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | 'presets': ['es2015'] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.es] 4 | indent_style = space 5 | indent_size = 2 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | *.js 4 | !gulpfile.babel.js 5 | package-lock.json 6 | .idea 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.es 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Original Author & Maintainer 2 | ---------------------------- 3 | grzhan (envy518@gmail.com) 4 | 5 | Contributors 6 | ------------ 7 | DualWield(xuli@shnow.cn) 8 | Yann Rocq (yann@rocq.net) 9 | Myer921 (Myer@morz.org) 10 | dixyes 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 grzhan 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hexo-tag-aplayer 2 | 3 | ![npm](https://img.shields.io/npm/v/hexo-tag-aplayer.svg) ![npm](https://img.shields.io/npm/l/hexo-tag-aplayer.svg) 4 | 5 | Embed APlayer([https://github.com/DIYgod/APlayer](https://github.com/DIYgod/APlayer)) in Hexo posts/pages. 6 | 7 | [中文文档](https://github.com/MoePlayer/hexo-tag-aplayer/blob/master/docs/README-zh_cn.md) 8 | 9 | 10 | 11 | 12 | 13 | - [Installation](#installation) 14 | - [Dependency](#dependency) 15 | - [Usage](#usage) 16 | - [Option](#option) 17 | - [With lyrics](#with-lyrics) 18 | - [With playlist](#with-playlist) 19 | - [MeingJS support (new in 3.0)](#meingjs-support-new-in-30) 20 | - [PJAX compatible](#pjax-compatible) 21 | - [Customization (new in 3.0)](#customization-new-in-30) 22 | - [Troubleshoot](#troubleshoot) 23 | - [Space within arguments](#space-within-arguments) 24 | - [Duplicate APlayer.JS loading](#duplicate-aplayerjs-loading) 25 | - [LICENSE](#license) 26 | 27 | 28 | 29 | 30 | 31 | 32 | ![plugin screenshot](http://7jpp1d.com1.z0.glb.clouddn.com/QQ20160202-5.png) 33 | 34 | 35 | ## Installation 36 | 37 | npm install --save hexo-tag-aplayer 38 | ## Dependency 39 | 40 | + APlayer.js >= 1.10.0 41 | + Meting.js >= 1.2.0 42 | 43 | ## Usage 44 | 45 | {% aplayer title author url [picture_url, narrow, autoplay, width:xxx, lrc:xxx] %} 46 | 47 | ### Option 48 | 49 | + `title` : music title 50 | + `author`: music author 51 | + `url`: music file url 52 | + `picture_url`: optional, music picture url 53 | + `narrow`: optional, narrow style 54 | + `autoplay`: optional, autoplay music, not supported by mobile browsers 55 | + `width:xxx`: optional, prefix `width:`, player's width (default: 100%) 56 | + `lrc:xxx`: optional, prefix `lrc:`, LRC file url 57 | 58 | With [post asset folders](https://hexo.io/docs/asset-folders.html#Tag-Plugins-For-Relative-Path-Referencing) enabled, you can easily place your image, music and LRC file into asset folder, and reference them like: 59 | 60 | {% aplayer "Caffeine" "Jeff Williams" "caffeine.mp3" "picture.jpg" "lrc:caffeine.txt" %} 61 | 62 | ### With lyrics 63 | 64 | Besides 'lrc' option, you can use `aplayerlrc` which has end tag to show lyrics. 65 | 66 | {% aplayerlrc "title" "author" "url" "autoplay" %} 67 | [00:00.00]lrc here 68 | {% endaplayerlrc %} 69 | 70 | ### With playlist 71 | 72 | {% aplayerlist %} 73 | { 74 | "narrow": false, // Optional, narrow style 75 | "autoplay": true, // Optional, autoplay song(s), not supported by mobile browsers 76 | "mode": "random", // Optional, play mode, can be `random` `single` `circulation`(loop) `order`(no loop), default: `circulation` 77 | "showlrc": 3, // Optional, show lrc, can be 1, 2, 3 78 | "mutex": true, // Optional, pause other players when this player playing 79 | "theme": "#e6d0b2", // Optional, theme color, default: #b7daff 80 | "preload": "metadata", // Optional, the way to load music, can be 'none' 'metadata' 'auto', default: 'auto' 81 | "listmaxheight": "513px", // Optional, max height of play list 82 | "music": [ 83 | { 84 | "title": "CoCo", 85 | "author": "Jeff Williams", 86 | "url": "caffeine.mp3", 87 | "pic": "caffeine.jpeg", 88 | "lrc": "caffeine.txt" 89 | }, 90 | { 91 | "title": "アイロニ", 92 | "author": "鹿乃", 93 | "url": "irony.mp3", 94 | "pic": "irony.jpg" 95 | } 96 | ] 97 | } 98 | {% endaplayerlist %} 99 | 100 | ### MeingJS support (new in 3.0) 101 | 102 | When you use MetingJS, your blog can play musics from Tencent, Netease, Xiami, Kugou, Baidu and more. 103 | 104 | See [metowolf/MetingJS](https://github.com/metowolf/MetingJS) and [metowolf/Meting](https://github.com/metowolf/Meting) in detail. 105 | 106 | If you want to use MetingJS in `hexo-tag-aplayer`, you need enable it in `_config.yml` 107 | 108 | ```yaml 109 | aplayer: 110 | meting: true 111 | ``` 112 | 113 | Now you can use `{% meting ...%}` in your post: 114 | 115 | ``` 116 | 117 | {% meting "60198" "netease" "playlist" %} 118 | 119 | 120 | {% meting "60198" "netease" "playlist" "autoplay" "mutex:false" "listmaxheight:340px" "preload:none" "theme:#ad7a86"%} 121 | ``` 122 | 123 | The `{% meting %}` options are shown below: 124 | 125 | | Option | Default | Description | 126 | | ------------- | ------------ | ------------------------------------------------------------ | 127 | | id | **required** | song id / playlist id / album id / search keyword | 128 | | server | **required** | Music platform: `netease`, `tencent`, `kugou`, `xiami`, `baidu` | 129 | | type | **required** | `song`, `playlist`, `album`, `search`, `artist` | 130 | | fixed | `false` | Enable fixed mode | 131 | | mini | `false` | Enable mini mode | 132 | | loop | `all` | Player loop play, values: 'all', 'one', 'none' | 133 | | order | `list` | Player play order, values: 'list', 'random' | 134 | | volume | 0.7 | Default volume, notice that player will remember user setting, default volume will not work after user set volume themselves | 135 | | lrctype | 0 | Lyric type | 136 | | listfolded | `false` | Indicate whether list should folded at first | 137 | | autoplay | `false` | Autoplay song(s), not supported by mobile browsers | 138 | | mutex | `true` | Pause other players when this player playing | 139 | | listmaxheight | `340px` | Max height of play list | 140 | | preload | `auto` | The way to load music, can be `none`, `metadata`, `auto` | 141 | | storagename | `metingjs` | LocalStorage key that store player setting | 142 | | theme | `#ad7a86` | Theme color | 143 | 144 | Read section [customization](#customization-new-in-30) to learn how to configure self-host meting api server in `hexo-tag-aplayer` and other configuration. 145 | 146 | ### PJAX compatible 147 | 148 | You need destroy APlayer instances manually when you use PJAX. 149 | 150 | ```js 151 | $(document).on('pjax:start', function () { 152 | if (window.aplayers) { 153 | for (let i = 0; i < window.aplayers.length; i++) { 154 | window.aplayers[i].destroy(); 155 | } 156 | window.aplayers = []; 157 | } 158 | }); 159 | ``` 160 | 161 | ## Customization (new in 3.0) 162 | 163 | You can configure `hexo-tag-aplayer` in `_config.yml`: 164 | 165 | ```yaml 166 | aplayer: 167 | script_dir: some/place # Script asset path in public directory, default: 'assets/js' 168 | style_dir: some/place # Style asset path in public directory, default: 'assets/css' 169 | cdn: http://xxx/aplayer.min.js # External APlayer.js url (CDN) 170 | style_cdn: http://xxx/aplayer.min.css # External APlayer.css url (CDN) 171 | meting: true # Meting support, default: false 172 | meting_api: http://xxx/api.php # Meting api url 173 | meting_cdn: http://xxx/Meing.min.js # External Meting.js url (CDN) 174 | asset_inject: true # Auto asset injection, default: true 175 | externalLink: http://xxx/aplayer.min.js # Deprecated, use 'cdn' instead 176 | ``` 177 | 178 | ## Troubleshoot 179 | 180 | ### Space within arguments 181 | 182 | Hexo has an [issue](https://github.com/hexojs/hexo/issues/1455) that cannot use space within tag arguments. 183 | 184 | If you encounter this problem, **install the latest (beta) version, and wrap the arguments within a string literal, for example:** 185 | 186 | ``` 187 | {% aplayer "Caffeine" "Jeff Williams" "caffeine.mp3" "autoplay" "width:70%" "lrc:caffeine.txt" %} 188 | ``` 189 | 190 | ### Duplicate APlayer.JS loading 191 | 192 | The plugin hooks filter `after_render:html` , and it would inject `APlayer.js` and `Meting.js` in ``: 193 | 194 | ```html 195 | 196 | 197 | ... 198 | 199 | 200 | 201 | ... 202 | 203 | ``` 204 | 205 | However, `after_render:html` is not fired in some cases : 206 | 207 | + [Does not work with hexo-renderer-jade](https://github.com/hexojs/hexo-inject/issues/1) 208 | + `after_render:html` seems not to get emitted in default settings of hexo server module (`hexo server`), it means you have to use static serving mode( `hexo server -s`) instead. 209 | 210 | In such cases, the plugin would hook`after_post_render` as a fallback, which has a possibility to cause duplicate asset loadings. 211 | 212 | If you want to solve this issue definitely, you can disable this auto-injection feature in `_config.yml` and insert the scripts by yourself: 213 | 214 | ```yaml 215 | aplayer: 216 | asset_inject: false 217 | ``` 218 | 219 | ## LICENSE 220 | 221 | MIT 222 | -------------------------------------------------------------------------------- /common/constant.es: -------------------------------------------------------------------------------- 1 | export const APLAYER_TAG_MARKER = `aplayer-tag-marker` 2 | export const APLAYER_SCRIPT_MARKER = `aplayer-script-marker` 3 | export const APLAYER_SECONDARY_SCRIPT_MARKER = `aplayer-secondary-script-marker` 4 | export const APLAYER_STYLE_MARKER = `aplayer-style-marker` 5 | export const APLAYER_SECONDARY_STYLE_MARKER = `aplayer-secondary-style-marker` 6 | export const METING_TAG_MARKER = `meting-tag-marker` 7 | export const METING_SCRIPT_MARKER = `meting-script-marker` 8 | export const METING_SECONDARY_SCRIPT_MARKER = `meting-secondary-script-marker` 9 | 10 | export const PLAYER_TAG_OPTION = { 11 | title: '', author: '', url: '', pic: '', 12 | narrow: false, autoplay: false, width: '', 13 | lrcOption: false, lrcPath: '' 14 | } 15 | 16 | export const METING_TAG_OPTION = { 17 | id: '', server: '', type: '', mode: 'circulation', 18 | autoplay: false, mutex: true, listmaxheight: '340px', 19 | preload: 'auto', theme: '#ad7a86' 20 | } -------------------------------------------------------------------------------- /common/util.es: -------------------------------------------------------------------------------- 1 | const escapeRegExp = (str) => { 2 | return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1") 3 | } 4 | 5 | export const generateRandomString = function(length) { 6 | const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' 7 | return Array.apply(null, {length}).map(() => ALPHABET.charAt(Math.floor(Math.random() * ALPHABET.length))).join('') 8 | } 9 | 10 | export const throwError = (message) => { 11 | throw new Error(`[hexo-tag-aplayer] ${message}`) 12 | } 13 | 14 | export const clone = (object) => { 15 | return JSON.parse(JSON.stringify(object)) 16 | } 17 | 18 | export const extractOptionValue = (pair) => { 19 | return pair.slice(pair.indexOf(':') + 1) 20 | } 21 | 22 | export const removeAll = (target, find) => { 23 | return target.replace(new RegExp(escapeRegExp(find), 'g'), '') 24 | } -------------------------------------------------------------------------------- /docs/README-zh_cn.md: -------------------------------------------------------------------------------- 1 | # hexo-tag-aplayer 2 | 3 | ![npm](https://img.shields.io/npm/v/hexo-tag-aplayer.svg) ![npm](https://img.shields.io/npm/l/hexo-tag-aplayer.svg) 4 | 5 | [APlayer](https://github.com/MoePlayer/APlayer) 播放器的 Hexo 标签插件(现已支持 [MetingJS](https://github.com/metowolf/MetingJS))。 6 | 7 | 8 | 9 | 10 | 11 | - [安装](#%E5%AE%89%E8%A3%85) 12 | - [依赖](#%E4%BE%9D%E8%B5%96) 13 | - [使用](#%E4%BD%BF%E7%94%A8) 14 | - [标签参数](#%E6%A0%87%E7%AD%BE%E5%8F%82%E6%95%B0) 15 | - [歌词标签](#%E6%AD%8C%E8%AF%8D%E6%A0%87%E7%AD%BE) 16 | - [播放列表](#%E6%92%AD%E6%94%BE%E5%88%97%E8%A1%A8) 17 | - [MeingJS 支持 (3.0 新功能)](#meingjs-%E6%94%AF%E6%8C%81-30-%E6%96%B0%E5%8A%9F%E8%83%BD) 18 | - [PJAX 兼容](#pjax-%E5%85%BC%E5%AE%B9) 19 | - [自定义配置(3.0 新功能)](#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%85%8D%E7%BD%AE%EF%BC%8830-%E6%96%B0%E5%8A%9F%E8%83%BD%EF%BC%89) 20 | - [故障排除](#%E6%95%85%E9%9A%9C%E6%8E%92%E9%99%A4) 21 | - [标签参数空格问题](#%E6%A0%87%E7%AD%BE%E5%8F%82%E6%95%B0%E7%A9%BA%E6%A0%BC%E9%97%AE%E9%A2%98) 22 | - [重复载入 Aplayer.js 资源脚本问题](#%E9%87%8D%E5%A4%8D%E8%BD%BD%E5%85%A5-aplayerjs-%E8%B5%84%E6%BA%90%E8%84%9A%E6%9C%AC%E9%97%AE%E9%A2%98) 23 | - [LICENSE](#license) 24 | 25 | 26 | 27 | 28 | 29 | ![plugin screenshot](http://7jpp1d.com1.z0.glb.clouddn.com/QQ20160202-5.png) 30 | 31 | ## 安装 32 | 33 | ``` 34 | npm install --save hexo-tag-aplayer 35 | ``` 36 | 37 | ## 依赖 38 | 39 | + APlayer.js > 1.8.0 40 | + Meting.js > 1.1.1 41 | 42 | ## 使用 43 | 44 | ``` 45 | {% aplayer title author url [picture_url, narrow, autoplay, width:xxx, lrc:xxx] %} 46 | ``` 47 | 48 | ### 标签参数 49 | 50 | - `title` : 曲目标题 51 | - `author`: 曲目作者 52 | - `url`: 音乐文件 URL 地址 53 | - `picture_url`: (可选) 音乐对应的图片地址 54 | - `narrow`: (可选)播放器袖珍风格 55 | - `autoplay`: (可选) 自动播放,移动端浏览器暂时不支持此功能 56 | - `width:xxx`: (可选) 播放器宽度 (默认: 100%) 57 | - `lrc:xxx`: (可选)歌词文件 URL 地址 58 | 59 | 当开启 Hexo 的 [文章资源文件夹](https://hexo.io/zh-cn/docs/asset-folders.html#%E6%96%87%E7%AB%A0%E8%B5%84%E6%BA%90%E6%96%87%E4%BB%B6%E5%A4%B9) 功能时,可以将图片、音乐文件、歌词文件放入与文章对应的资源文件夹中,然后直接引用: 60 | 61 | ``` 62 | {% aplayer "Caffeine" "Jeff Williams" "caffeine.mp3" "picture.jpg" "lrc:caffeine.txt" %} 63 | ``` 64 | 65 | ### 歌词标签 66 | 67 | 除了使用标签 `lrc` 选项来设定歌词,你也可以直接使用 `aplayerlrc` 标签来直接插入歌词文本在博客中: 68 | 69 | ``` 70 | {% aplayerlrc "title" "author" "url" "autoplay" %} 71 | [00:00.00]lrc here 72 | {% endaplayerlrc %} 73 | ``` 74 | 75 | ### 播放列表 76 | 77 | ``` 78 | {% aplayerlist %} 79 | { 80 | "narrow": false, // (可选)播放器袖珍风格 81 | "autoplay": true, // (可选) 自动播放,移动端浏览器暂时不支持此功能 82 | "mode": "random", // (可选)曲目循环类型,有 'random'(随机播放), 'single' (单曲播放), 'circulation' (循环播放), 'order' (列表播放), 默认:'circulation' 83 | "showlrc": 3, // (可选)歌词显示配置项,可选项有:1,2,3 84 | "mutex": true, // (可选)该选项开启时,如果同页面有其他 aplayer 播放,该播放器会暂停 85 | "theme": "#e6d0b2", // (可选)播放器风格色彩设置,默认:#b7daff 86 | "preload": "metadata", // (可选)音乐文件预载入模式,可选项: 'none' 'metadata' 'auto', 默认: 'auto' 87 | "listmaxheight": "513px", // (可选) 该播放列表的最大长度 88 | "music": [ 89 | { 90 | "title": "CoCo", 91 | "author": "Jeff Williams", 92 | "url": "caffeine.mp3", 93 | "pic": "caffeine.jpeg", 94 | "lrc": "caffeine.txt" 95 | }, 96 | { 97 | "title": "アイロニ", 98 | "author": "鹿乃", 99 | "url": "irony.mp3", 100 | "pic": "irony.jpg" 101 | } 102 | ] 103 | } 104 | {% endaplayerlist %} 105 | ``` 106 | 107 | ### MeingJS 支持 (3.0 新功能) 108 | 109 | [MetingJS](https://github.com/metowolf/MetingJS) 是基于[Meting API](https://github.com/metowolf/Meting) 的 APlayer 衍生播放器,引入 MetingJS 后,播放器将支持对于 QQ音乐、网易云音乐、虾米、酷狗、百度等平台的音乐播放。 110 | 111 | 如果想在本插件中使用 MetingJS,请在 Hexo 配置文件 `_config.yml` 中设置: 112 | 113 | ```yaml 114 | aplayer: 115 | meting: true 116 | ``` 117 | 118 | 接着就可以通过 `{% meting ...%}` 在文章中使用 MetingJS 播放器了: 119 | 120 | ``` 121 | 122 | {% meting "60198" "netease" "playlist" %} 123 | 124 | 125 | {% meting "60198" "netease" "playlist" "autoplay" "mutex:false" "listmaxheight:340px" "preload:none" "theme:#ad7a86"%} 126 | ``` 127 | 128 | 有关 `{% meting %}` 的选项列表如下: 129 | 130 | | 选项 | 默认值 | 描述 | 131 | | ------------- | ---------- | ----------------------------------------------------------- | 132 | | id | **必须值** | 歌曲 id / 播放列表 id / 相册 id / 搜索关键字 | 133 | | server | **必须值** | 音乐平台: `netease`, `tencent`, `kugou`, `xiami`, `baidu` | 134 | | type | **必须值** | `song`, `playlist`, `album`, `search`, `artist` | 135 | | fixed | `false` | 开启固定模式 | 136 | | mini | `false` | 开启迷你模式 | 137 | | loop | `all` | 列表循环模式:`all`, `one`,`none` | 138 | | order | `list` | 列表播放模式: `list`, `random` | 139 | | volume | 0.7 | 播放器音量 | 140 | | lrctype | 0 | 歌词格式类型 | 141 | | listfolded | `false` | 指定音乐播放列表是否折叠 | 142 | | storagename | `metingjs` | LocalStorage 中存储播放器设定的键名 | 143 | | autoplay | `true` | 自动播放,移动端浏览器暂时不支持此功能 | 144 | | mutex | `true` | 该选项开启时,如果同页面有其他 aplayer 播放,该播放器会暂停 | 145 | | listmaxheight | `340px` | 播放列表的最大长度 | 146 | | preload | `auto` | 音乐文件预载入模式,可选项: `none`, `metadata`, `auto` | 147 | | theme | `#ad7a86` | 播放器风格色彩设置 | 148 | 149 | 关于如何设置自建的 Meting API 服务器地址,以及其他 MetingJS 配置,请参考章节[自定义配置](#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%85%8D%E7%BD%AE30-%E6%96%B0%E5%8A%9F%E8%83%BD) 150 | 151 | ### PJAX 兼容 152 | 153 | 若在 Hexo 中使用了 PJAX,可能需要自己手动清理 APlayer 全局实例: 154 | 155 | ```js 156 | $(document).on('pjax:start', function () { 157 | if (window.aplayers) { 158 | for (let i = 0; i < window.aplayers.length; i++) { 159 | window.aplayers[i].destroy(); 160 | } 161 | window.aplayers = []; 162 | } 163 | }); 164 | ``` 165 | 166 | ## 自定义配置(3.0 新功能) 167 | 168 | 现在你可以在 Hexo 配置文件 `_config.yml` 中配置本插件: 169 | 170 | ```yaml 171 | aplayer: 172 | script_dir: some/place # Public 目录下脚本目录路径,默认: 'assets/js' 173 | style_dir: some/place # Public 目录下样式目录路径,默认: 'assets/css' 174 | cdn: http://xxx/aplayer.min.js # 引用 APlayer.js 外部 CDN 地址 (默认不开启) 175 | style_cdn: http://xxx/aplayer.min.css # 引用 APlayer.css 外部 CDN 地址 (默认不开启) 176 | meting: true # MetingJS 支持 177 | meting_api: http://xxx/api.php # 自定义 Meting API 地址 178 | meting_cdn: http://xxx/Meing.min.js # 引用 Meting.js 外部 CDN 地址 (默认不开启) 179 | asset_inject: true # 自动插入 Aplayer.js 与 Meting.js 资源脚本, 默认开启 180 | externalLink: http://xxx/aplayer.min.js # 老版本参数,功能与参数 cdn 相同 181 | ``` 182 | 183 | ## 故障排除 184 | 185 | ### 标签参数空格问题 186 | 187 | 在 Hexo 标签中,用户可能无法直接在标签参数中[加入空格](https://github.com/hexojs/hexo/issues/1455) 188 | 189 | 如果遇到这类问题,请直接将参数用双引号括起来使用,如下所示: 190 | 191 | ``` 192 | {% aplayer "Caffeine" "Jeff Williams" "caffeine.mp3" "autoplay" "width:70%" "lrc:caffeine.txt" %} 193 | ``` 194 | 195 | ### 重复载入 Aplayer.js 资源脚本问题 196 | 197 | 本插件通过 `after_render:html`过滤器 , 将 `APlayer.js` 和 `Meting.js` 插入到使用了本插件标签 的 HTML 文件中: 198 | 199 | ```html 200 | 201 | 202 | ... 203 | 204 | 205 | 206 | ... 207 | 208 | ``` 209 | 210 | 但是 `after_render:html` 在一些情形下可能无法被正常触发: 211 | 212 | - [Does not work with hexo-renderer-jade](https://github.com/hexojs/hexo-inject/issues/1) 213 | - `after_render:html` 似乎在 Hexo 服务器模式默认配置中无法被调用 (`hexo server`), 遇到这种情况用户可能需要使用 `hexo-server` 的静态文件解析模式 ( `hexo server -s`) . 214 | 215 | 如果在博客生成过程中,插件发现 `after_render:html` 没有被调用,那么插件将会通过 `after_post_render` 过滤器来植入脚本。但是使用 `after_post_render` 会有重复载入 `APlayer.js` 的情况(例如当一个页面中存在多篇博客时),以及一些非文章页面将无法使用本插件。 216 | 217 | 如果想完全解决这个问题,用户可能需要自己在主题文件中手动加入 `Aplayer.js` 与 `Meting.js`,同时关闭插件的自动脚本插入功能: 218 | 219 | ```yaml 220 | aplayer: 221 | asset_inject: false 222 | ``` 223 | 224 | ## LICENSE 225 | 226 | MIT -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import gulp from 'gulp'; 4 | import babel from 'gulp-babel'; 5 | import rename from 'gulp-rename'; 6 | import clean from 'gulp-clean'; 7 | import watch from 'gulp-watch'; 8 | import plumber from 'gulp-plumber'; 9 | 10 | const LOCAL_ENV_PATH = process.env.HEXO_TAG_APLAYER_LOCAL_PATH; 11 | const sources = ['index.es', 'lib/**/*.es', 'common/**/*.es']; 12 | const resetExt = (path) => { 13 | path.extname = '.js'; 14 | console.log(JSON.stringify(path)) 15 | }; 16 | 17 | gulp.task('default', ['build']); 18 | 19 | gulp.task('build', () => { 20 | return gulp.src(sources, {base: '.'}) 21 | .pipe(babel({ 22 | 'presets': ['es2015'] 23 | })) 24 | .pipe(rename(resetExt)) 25 | .pipe(gulp.dest('.')); 26 | }); 27 | 28 | gulp.task('local-test', () => { 29 | return gulp.src(sources, {base: '.'}) 30 | .pipe(babel({ 31 | 'presets': ['es2015'] 32 | })) 33 | .pipe(rename(resetExt)) 34 | .pipe(gulp.dest(LOCAL_ENV_PATH)); 35 | }); 36 | 37 | gulp.task('clean', () => { 38 | const builds = sources.map((s) => s.replace('es', 'js')); 39 | return gulp.src(builds, {read: false}) 40 | .pipe(clean()); 41 | }); 42 | 43 | gulp.task('watch', () => { 44 | return watch(sources, () => { 45 | return gulp.src(sources, {base: '.'}) 46 | .pipe(watch(sources)) 47 | .pipe(plumber(err => console.log(err.stack))) 48 | .pipe(babel({ 49 | 'presets': ['es2015'] 50 | })) 51 | .pipe(plumber.stop()) 52 | .pipe(rename(resetExt)) 53 | .pipe(gulp.dest(LOCAL_ENV_PATH)); 54 | }); 55 | }); -------------------------------------------------------------------------------- /index.deprecated.es: -------------------------------------------------------------------------------- 1 | /** 2 | * hexo-tag-aplayer 3 | * https://github.com/grzhan/hexo-tag-aplayer 4 | * Copyright (c) 2016, grzhan 5 | * Licensed under the MIT license. 6 | * 7 | * Syntax: 8 | * {% aplayer title author url [picture_url, narrow, autoplay] %} 9 | */ 10 | require('babel-polyfill'); 11 | let counter = 0; 12 | const fs = require('hexo-fs'), 13 | util = require('hexo-util'), 14 | path = require('path'), 15 | urllib = require('url'), 16 | srcDir = path.dirname(require.resolve('aplayer')), 17 | scriptDir = path.join('assets', 'js/'), 18 | aplayerScript = 'APlayer.min.js', 19 | registers = [ 20 | [aplayerScript, scriptDir + aplayerScript, path.join(srcDir, aplayerScript)] 21 | ]; 22 | 23 | const preProcessUrl = (id, url) => { 24 | if (url.startsWith('https://') || url.startsWith('http://') || url.startsWith('/')) { 25 | return url; 26 | } 27 | const PostAsset = hexo.model('PostAsset') 28 | const asset = PostAsset.findOne({post: id, slug: url}); 29 | if (!asset) throw new Error(`Specified music file not found ${url}`); 30 | return urllib.resolve(hexo.config.root, asset.path); 31 | }; 32 | 33 | registers.forEach((register) => { 34 | const [regName, path, srcPath] = register; 35 | hexo.extend.generator.register(regName, () => { 36 | return { 37 | path, 38 | config() { 39 | return fs.createReadStream(srcPath); 40 | } 41 | }; 42 | }); 43 | }); 44 | 45 | const generateAPlayerUrl = () => { 46 | const aplayerConfig = hexo.config.aplayer || {}; 47 | if (aplayerConfig.externalLink) { 48 | return aplayerConfig.externalLink; 49 | } else { 50 | const root = hexo.config.root ? hexo.config.root : '/'; 51 | return path.join(root, '/', scriptDir, aplayerScript); 52 | } 53 | }; 54 | 55 | hexo.extend.filter.register('after_post_render', (data) => { 56 | data.content = 57 | util.htmlTag('script', {src: generateAPlayerUrl()}, ' ') + 58 | data.content; 59 | return data; 60 | }); 61 | 62 | // {% aplayer title author url [picture_url, narrow, autoplay] %} 63 | hexo.extend.tag.register('aplayer', function(args) { 64 | let [title, author, url] = args, lrcPath = '', 65 | narrow = false, autoplay = false, lrcOpt = false, width = '', 66 | pic = args[3] && args[3] !== 'narrow' && args[3] !== 'autoplay' 67 | && !args[3].includes('lrc:') && !args[3].includes('width:') ? preProcessUrl(this._id, args[3]) : '', 68 | id = 'aplayer' + (counter++), raw = '', content = ''; 69 | url = preProcessUrl(this._id, url); 70 | // Parse optional arguments 71 | if (args.length > 3) { 72 | let options = args.slice(3); 73 | narrow = options.includes('narrow'); 74 | autoplay = options.includes('autoplay'); 75 | for (let i = 0; i < options.length; i++) { 76 | let option = options[i]; 77 | lrcOpt = option.indexOf('lrc:') == 0 ? true : lrcOpt; 78 | lrcPath = option.indexOf('lrc:') == 0 ? option.slice(option.indexOf(':') + 1) : lrcPath; 79 | width = option.indexOf('width:') == 0 ? option + ';' : width; 80 | } 81 | } 82 | width = narrow ? '' : width; 83 | raw = `
`; 84 | if (lrcOpt) { 85 | // Generate lyric texts 86 | if (lrcPath.indexOf('http:') <0 && lrcPath.indexOf('https:') <0) { 87 | const PostAsset = hexo.database._models.PostAsset; 88 | const _path = path.join(hexo.base_dir, PostAsset.findOne({post: this._id, slug: lrcPath})._id); 89 | content = fs.readFileSync(_path); 90 | raw += `
${content}
`; 91 | } 92 | } 93 | raw += 94 | `
95 | `; 112 | return raw; 113 | }); 114 | 115 | // {% aplayerlrc "title" "author" "url" "autoplay" %} [00:00.00]lrc here {% endaplayerlrc %} 116 | hexo.extend.tag.register('aplayerlrc', function(args, content) { 117 | let [title, author, url] = args, 118 | narrow = false, autoplay = false, 119 | pic = args[3] && args[3] !== 'narrow' && args[3] !== 'autoplay' 120 | && !args[3].includes('lrc:') && !args[3].includes('width:') ? preProcessUrl(this._id, args[3]) : '', 121 | id = 'aplayer' + (counter++), raw = '', width = ''; 122 | url = preProcessUrl(this._id, url); 123 | if (args.length > 3) { 124 | const options = args.slice(3); 125 | narrow = options.includes('narrow'); 126 | autoplay = options.indexOf('autoplay'); 127 | for (let i = 0; i < options.length; i++) { 128 | const option = options[i]; 129 | width = option.indexOf('width:') == 0 ? option + ';' : width; 130 | } 131 | } 132 | width = narrow ? '' : width; 133 | raw = `
134 |
${content}
135 |
136 | `; 152 | return raw; 153 | }, {ends: true}); 154 | 155 | // {% aplayerlist %} {options} {% endaplayerlist %} 156 | hexo.extend.tag.register('aplayerlist', function(args, content) { 157 | try { 158 | const options = JSON.parse(content); 159 | const id = 'aplayer' + (counter++); 160 | const defaultOptions = { 161 | narrow: false, 162 | autoplay: false, 163 | showlrc: 0 164 | }; 165 | const resultOptions = Object.assign({}, defaultOptions, options); 166 | resultOptions.music.forEach(info => { 167 | info.url = preProcessUrl(this._id, info.url); 168 | info.pic = info.pic ? preProcessUrl(this._id, info.pic) : ''; 169 | }); 170 | const raw = ` 171 |
172 | 179 | 180 | `; 181 | return raw; 182 | } catch (e) { 183 | console.error(e); 184 | return ` 185 | `; 188 | } 189 | 190 | }, {ends: true}); 191 | -------------------------------------------------------------------------------- /index.es: -------------------------------------------------------------------------------- 1 | /** 2 | * hexo-tag-aplayer 3 | * https://github.com/grzhan/hexo-tag-aplayer 4 | * Copyright (c) 2016, grzhan 5 | * Licensed under the MIT license. 6 | * 7 | * Syntax: 8 | * {% aplayer title author url [picture_url, narrow, autoplay] %} 9 | */ 10 | require('babel-polyfill') 11 | 12 | import fs from 'hexo-fs' 13 | import {throwError} from "./common/util" 14 | import util from 'hexo-util' 15 | import { 16 | APLAYER_SCRIPT_MARKER, APLAYER_TAG_MARKER, APLAYER_SECONDARY_SCRIPT_MARKER, 17 | METING_TAG_MARKER, METING_SCRIPT_MARKER, METING_SECONDARY_SCRIPT_MARKER, APLAYER_SECONDARY_STYLE_MARKER, 18 | APLAYER_STYLE_MARKER 19 | } from './common/constant' 20 | import MetingTag from "./lib/tag/playerMeting" 21 | import APlayerTag from './lib/tag/player' 22 | import APlayerLyricTag from './lib/tag/playerLyric' 23 | import APlayerListTag from './lib/tag/playerList' 24 | import PartialView from './lib/view' 25 | import Config from './lib/config' 26 | 27 | const log = require('hexo-log')({name: 'hexo-tag-aplayer', debug: false}) 28 | const config = new Config(hexo) 29 | const APLAYER_STYLE_LITERAL = `` 30 | const APLAYER_SCRIPT_LITERAL = `` 31 | const METING_SCRIPT_LITERAL = config.get('meting_api') 32 | ? `` 33 | : `` 34 | let filterEmitted = {after_render: false, after_post_render: false} 35 | 36 | 37 | config.get('assets').forEach(asset => { 38 | const [external, name, dstPath, srcPath] = asset 39 | if (!external && config.get('asset_inject') && fs.existsSync(srcPath)) { 40 | hexo.extend.generator.register(name, () => { 41 | return { 42 | path: dstPath, 43 | data() { 44 | return fs.createReadStream(srcPath) 45 | } 46 | } 47 | }) 48 | } 49 | }) 50 | 51 | hexo.extend.filter.register('after_render:html', function(raw, info) { 52 | filterEmitted.after_render = true 53 | if (!config.get('asset_inject')) { 54 | return 55 | } 56 | const view = new PartialView(raw, info) 57 | if (view.isFullPage()) { 58 | if (!view.hasHeadTag()) { 59 | log.warn(`[hexo-tag-aplayer]: not found in ${view.path}, unable to inject script (like 'APlayer.js') in this page.`) 60 | return 61 | } 62 | // Inject APlayer script 63 | if (view.hasTagMarker(APLAYER_TAG_MARKER) && !view.assetAlreadyInjected(APLAYER_SCRIPT_MARKER)) { 64 | view.injectAsset(``) 65 | view.injectAsset(util.htmlTag('script', {src: config.get('script'), class: APLAYER_SCRIPT_MARKER}, '')) 66 | 67 | } 68 | // Inject Meting script 69 | if (config.get('meting') && view.hasTagMarker(METING_TAG_MARKER) && !view.assetAlreadyInjected(METING_SCRIPT_MARKER)) { 70 | if (config.get('meting_api')) { 71 | view.injectAsset( ``) 72 | } 73 | view.injectAsset(util.htmlTag('script', {src: config.get('meting_script'), class: METING_SCRIPT_MARKER}, '')) 74 | } 75 | // Remove duplicate scripts 76 | view.removeLiteral(APLAYER_SCRIPT_LITERAL) 77 | view.removeLiteral(METING_SCRIPT_LITERAL) 78 | view.removeLiteral(APLAYER_STYLE_LITERAL) 79 | } 80 | return view.content 81 | }) 82 | 83 | hexo.extend.filter.register('after_post_render', (data) => { 84 | filterEmitted.after_post_render = true 85 | if (!config.get('asset_inject')) { 86 | return 87 | } 88 | // Polyfill: filter 'after_render:html' may not be fired in some cases, see https://github.com/hexojs/hexo-inject/issues/1 89 | if (config.get('meting')) { 90 | data.content = METING_SCRIPT_LITERAL + data.content 91 | } 92 | data.content = APLAYER_STYLE_LITERAL + APLAYER_SCRIPT_LITERAL + data.content 93 | return data 94 | }) 95 | 96 | hexo.extend.tag.register('aplayer', function(args) { 97 | try { 98 | const tag = new APlayerTag(hexo, args, this._id) 99 | const output = tag.generate() 100 | return output 101 | } catch (e) { 102 | console.error(e); 103 | return ` 104 | `; 107 | } 108 | }) 109 | 110 | hexo.extend.tag.register('aplayerlrc', function(args, content) { 111 | try { 112 | const tag = new APlayerLyricTag(hexo, args, this._id, content) 113 | const output = tag.generate() 114 | return output 115 | } catch (e) { 116 | console.error(e); 117 | return ` 118 | ` 121 | } 122 | }, {ends: true}) 123 | 124 | 125 | hexo.extend.tag.register('aplayerlist', function(args, content) { 126 | try { 127 | const tag = new APlayerListTag(hexo, content, this._id) 128 | const output = tag.generate() 129 | return output 130 | } catch (e) { 131 | console.error(e) 132 | return ` 133 | ` 136 | } 137 | }, {ends: true}) 138 | 139 | 140 | hexo.extend.tag.register('meting', function(args) { 141 | try { 142 | if (!config.get('meting')) { 143 | throwError('Meting support is disabled, cannot resolve the meting tags properly.') 144 | } 145 | const tag = new MetingTag(hexo, args, this._id) 146 | const output = tag.generate() 147 | return output 148 | } catch (e) { 149 | console.error(e) 150 | return ` 151 | ` 154 | } 155 | }) 156 | 157 | hexo.extend.tag.register('before_exit', function() { 158 | if (!filterEmitted.after_render && filterEmitted.after_post_render) { 159 | log.warn('Filter "after_render:html" not emitted during this generation, duplicate scripts would not be removed.') 160 | } 161 | }) 162 | -------------------------------------------------------------------------------- /lib/config.es: -------------------------------------------------------------------------------- 1 | import fs from 'hexo-fs' 2 | import path from 'path' 3 | import {clone} from '../common/util' 4 | const APLAYER_DIR = path.dirname(require.resolve('aplayer')) 5 | const METING_DIR = path.dirname(require.resolve('meting')) 6 | const APLAYER_FILENAME = 'APlayer.min.js' 7 | const APLAYER_STYLENAME = 'APlayer.min.css' 8 | const METING_FILENAME = `Meting.min.js` 9 | const DEFAULT_SCRIPT_DIR = path.join('assets', 'js/') 10 | const DEFAULT_STYLE_DIR = path.join('assets', 'css/') 11 | const ASSETS = [ 12 | // 四元组:引用非本地文件标识符,文件名, 文件的目标部署路径, 资源文件源路径 13 | [false, APLAYER_FILENAME, path.join(DEFAULT_SCRIPT_DIR, APLAYER_FILENAME), path.join(APLAYER_DIR, APLAYER_FILENAME)], 14 | [false, APLAYER_STYLENAME, path.join(DEFAULT_STYLE_DIR, APLAYER_STYLENAME), path.join(APLAYER_DIR, APLAYER_STYLENAME)], 15 | [false, METING_FILENAME, path.join(DEFAULT_SCRIPT_DIR, METING_FILENAME), path.join(METING_DIR, METING_FILENAME)] 16 | ] 17 | 18 | /* 19 | * Aplayer configuration example in _config.yml: 20 | * 21 | * aplayer: 22 | * script_dir: some/place # Script asset path in public directory, default: 'assets/js' 23 | * style_dir: some/palce # Style asset path in public directory, default: 'assets/css' 24 | * cdn: http://xxx/aplayer.min.js # External APlayer.js url 25 | * style_cdn: http://xxx/aplayer.min.css # External APlayer.css url 26 | * meting: true # Meting support, default: false 27 | * meting_api: http://xxx/api.php # Meting api url 28 | * meting_cdn: http://xxx/Meing.min.js # External Meting.js url 29 | * externalLink: http://xxx/aplayer.min.js # Deprecated, use 'cdn' instead 30 | * asset_inject: true # Auto asset injection, default: true 31 | * */ 32 | export default class Config { 33 | constructor(hexo) { 34 | this.root = hexo.config.root ? hexo.config.root : '/' 35 | this.config = { 36 | assets: ASSETS, 37 | asset_inject: true, 38 | script_dir: DEFAULT_SCRIPT_DIR, 39 | style_dir: DEFAULT_STYLE_DIR, 40 | script: path.join(this.root, '/', DEFAULT_SCRIPT_DIR, APLAYER_FILENAME), 41 | style: path.join(this.root, '/', DEFAULT_STYLE_DIR, APLAYER_STYLENAME), 42 | meting: false, meting_api: null, 43 | meting_script: path.join(this.root, '/', DEFAULT_SCRIPT_DIR, METING_FILENAME), 44 | } 45 | if (hexo.config.aplayer) { 46 | this._parse(clone(hexo.config.aplayer)) 47 | } 48 | } 49 | 50 | _parse(source) { 51 | let isExternal = {aplayer: false, aplayerStyle: false, meting: false} 52 | // Parse script_dir 53 | if (source.script_dir) { 54 | this.set('script_dir', source.script_dir) 55 | } 56 | // Parse style_dir 57 | if (source.style_dir) { 58 | this.set('style_dir', source.style_dir) 59 | } 60 | // Asset auto-injection 61 | if (source.asset_inject === false) { 62 | this.set('asset_inject', source.asset_inject) 63 | } 64 | // Deprecated: externalLink option 65 | if (source.externalLink) { 66 | source.cdn = source.externalLink 67 | } 68 | // Parse aplayer external script 69 | if (source.cdn) { 70 | this.set('script', source.cdn) 71 | isExternal.aplayer = true 72 | } else { 73 | this.set('script', path.join(this.root, '/', this.get('script_dir'), APLAYER_FILENAME)) 74 | } 75 | // Parse aplayer external style 76 | if (source.style_cdn) { 77 | this.set('style', source.style_cdn) 78 | isExternal.aplayerStyle = true 79 | } else { 80 | this.set('style', path.join(this.root, '/', this.get('style_dir'), APLAYER_STYLENAME)) 81 | } 82 | let assets = [ 83 | [isExternal['aplayer'], APLAYER_FILENAME, path.join(this.get('script_dir'), APLAYER_FILENAME), 84 | path.join(APLAYER_DIR, APLAYER_FILENAME)], 85 | [isExternal['aplayerStyle'], APLAYER_STYLENAME, path.join(this.get('style_dir'), APLAYER_STYLENAME), 86 | path.join(APLAYER_DIR, APLAYER_STYLENAME)] 87 | ] 88 | // Meting Config 89 | if (source.meting !== false) { 90 | this.set('meting', source.meting) 91 | if (source.meting_cdn) { 92 | this.set('meting_script', source.meting_cdn) 93 | isExternal.meting = true 94 | } else { 95 | this.set('meting_script', path.join(this.root, '/', this.get('script_dir'), METING_FILENAME)) 96 | } 97 | if (source.meting_api) { 98 | this.set('meting_api', source.meting_api) 99 | } 100 | assets.push([isExternal['meting'], METING_FILENAME, path.join(this.get('script_dir'), METING_FILENAME), 101 | path.join(METING_DIR, METING_FILENAME)]) 102 | } 103 | // Reset assets config 104 | this.set('assets', assets) 105 | } 106 | 107 | get(name) { 108 | return this.config[name] 109 | } 110 | 111 | set(name, value) { 112 | this.config[name] = value 113 | } 114 | } -------------------------------------------------------------------------------- /lib/tag/base.es: -------------------------------------------------------------------------------- 1 | import urllib from 'url' 2 | import {throwError, generateRandomString} from '../../common/util' 3 | 4 | export class BaseTag { 5 | /* 6 | args: Tag arguments 7 | id: Instance ID 8 | pid: Post ID 9 | */ 10 | constructor(hexo, args, pid) { 11 | this.config = hexo.config.aplayer || {} 12 | this.pid = pid 13 | this.id = `aplayer-${generateRandomString(8)}` 14 | this.hexo = hexo 15 | } 16 | 17 | processUrl(url) { 18 | if (/^https?:\/\/|^\//.test(url)) { 19 | return url 20 | } 21 | const PostAsset = this.hexo.model('PostAsset') 22 | const asset = PostAsset.findOne({post: this.pid, slug: url}) 23 | if (!asset) throwError(`Specified asset file not found (${url})`) 24 | return urllib.resolve(this.hexo.config.root, asset.path) 25 | } 26 | 27 | parse() { 28 | throwError("Unimplemented method: parse") 29 | } 30 | 31 | generate() { 32 | throwError('Unimplemented method: generate') 33 | } 34 | } -------------------------------------------------------------------------------- /lib/tag/player.es: -------------------------------------------------------------------------------- 1 | import fs from 'hexo-fs' 2 | import path from 'path' 3 | import {BaseTag} from "./base" 4 | import {PLAYER_TAG_OPTION, APLAYER_TAG_MARKER} from "../../common/constant" 5 | import {throwError, extractOptionValue} from "../../common/util" 6 | 7 | export default class APlayerTag extends BaseTag { 8 | constructor(hexo, args, pid) { 9 | super(hexo, args, pid) 10 | this.settings = this.parse(args) 11 | } 12 | 13 | parse(options) { 14 | let settings = Object.assign({}, PLAYER_TAG_OPTION); 15 | ([settings.title, settings.author, settings.url] = options) 16 | const optionalArgs = options.slice(3) 17 | optionalArgs.forEach((value,index) => { 18 | switch(true) { 19 | case value === 'narrow': 20 | settings.narrow = true 21 | break 22 | case value === 'autoplay': 23 | settings.autoplay = true 24 | break 25 | case /^lrc:/.test(value): 26 | settings.lrcOption = 1 27 | settings.lrcPath = extractOptionValue(value) 28 | break 29 | case /^width:/.test(value): 30 | settings.width = value + ';' 31 | break 32 | case index === 0: 33 | settings.pic = this.processUrl(value) 34 | break 35 | default: 36 | throwError(`Unrecognized tag argument(${index+1}): ${value}`) 37 | } 38 | }) 39 | settings.width = settings.narrow ? '' : settings.width 40 | return settings 41 | } 42 | 43 | generate() { 44 | const hexo = this.hexo 45 | let content = '' 46 | let {title, author, url, narrow, pic, 47 | autoplay, lrcOption, lrcPath, width} = this.settings 48 | if (lrcOption) { 49 | if (!/^https?/.test(lrcPath)) { 50 | const PostAsset = hexo.database._models.PostAsset 51 | const _path = path.join(hexo.base_dir, PostAsset.findOne({post: this.pid, slug: lrcPath})._id) 52 | content = fs.readFileSync(_path) 53 | lrcOption = 2 54 | } else { 55 | lrcOption = 3 56 | } 57 | } 58 | return ` 59 |
60 |
${content}
61 |
62 | ` 79 | } 80 | } -------------------------------------------------------------------------------- /lib/tag/playerList.es: -------------------------------------------------------------------------------- 1 | import {BaseTag} from "./base" 2 | import {APLAYER_TAG_MARKER} from "../../common/constant" 3 | 4 | 5 | export default class APlayerListTag extends BaseTag { 6 | constructor(hexo, args,pid) { 7 | super(hexo, args, pid) 8 | this.settings = this.parse(args) 9 | } 10 | 11 | parse(options) { 12 | let settings = Object.assign({ 13 | narrow: false, 14 | autoplay: false, 15 | showlrc: 0 16 | }, JSON.parse(options)) 17 | settings.music.forEach(info => { 18 | info.url = this.processUrl(info.url) 19 | info.pic = info.pic ? this.processUrl(info.pic) : '' 20 | }) 21 | return settings 22 | } 23 | 24 | generate() { 25 | const settings = JSON.stringify(this.settings) 26 | return ` 27 |
28 | ` 35 | } 36 | } -------------------------------------------------------------------------------- /lib/tag/playerLyric.es: -------------------------------------------------------------------------------- 1 | import {BaseTag} from "./base" 2 | import {APLAYER_TAG_MARKER, PLAYER_TAG_OPTION} from "../../common/constant" 3 | import {throwError} from "../../common/util" 4 | 5 | export default class APlayerLyricTag extends BaseTag { 6 | constructor(hexo, args, pid, lyrics) { 7 | super(hexo, args, pid) 8 | this.settings = this.parse(args) 9 | this.lyrics = lyrics 10 | } 11 | 12 | parse(options) { 13 | let settings = Object.assign({}, PLAYER_TAG_OPTION); 14 | ([settings.title, settings.author, settings.url] = options) 15 | const optionalArgs = options.slice(3) 16 | optionalArgs.forEach((value,index) => { 17 | switch(true) { 18 | case value === 'narrow': 19 | settings.narrow = true 20 | break 21 | case value === 'autoplay': 22 | settings.autoplay = true 23 | break 24 | case /^width:/.test(value): 25 | settings.width = value + ';' 26 | break 27 | case index === 0: 28 | settings.pic = this.processUrl(value) 29 | break 30 | default: 31 | throwError(`Unrecognized tag argument(${index+1}): ${value}`) 32 | } 33 | }) 34 | settings.width = settings.narrow ? '' : settings.width 35 | return settings 36 | } 37 | 38 | generate() { 39 | let {title, author, url, narrow, pic, 40 | autoplay, width} = this.settings 41 | return `
42 |
${this.lyrics}
43 |
44 | ` 60 | } 61 | } -------------------------------------------------------------------------------- /lib/tag/playerMeting.es: -------------------------------------------------------------------------------- 1 | import {METING_TAG_OPTION, METING_TAG_MARKER, APLAYER_TAG_MARKER} from '../../common/constant' 2 | import {throwError, extractOptionValue} from '../../common/util' 3 | import {BaseTag} from "./base" 4 | 5 | export default class MetingTag extends BaseTag { 6 | constructor(hexo, args, pid) { 7 | super(hexo, args, pid) 8 | this.settings = this.parse(args) 9 | } 10 | 11 | parse(options) { 12 | let settings = Object.assign({}, METING_TAG_OPTION); 13 | ([settings.id, settings.server, settings.type] = options) 14 | const optionalArgs = options.slice(3) 15 | optionalArgs.forEach((option, index) => { 16 | switch (true) { 17 | case option === 'autoplay': 18 | settings.autoplay = true 19 | break 20 | case option === 'fixed': 21 | settings.fixed = true 22 | break 23 | case option === 'mini': 24 | settings.mini = true 25 | break; 26 | case option.startsWith('loop:'): 27 | settings.loop = extractOptionValue(option); 28 | break; 29 | case option.startsWith('order:'): 30 | settings.order = extractOptionValue(option); 31 | break; 32 | case option.startsWith('volume:'): 33 | settings.volume = extractOptionValue(option); 34 | break; 35 | case option.startsWith('lrctype:'): 36 | settings.lrctype = extractOptionValue(option); 37 | break; 38 | case option === 'listfolded': 39 | settings.listfolded = true; 40 | break; 41 | case option.startsWith('storagename:'): 42 | settings.storagename = extractOptionValue(option); 43 | break; 44 | case option.startsWith('mutex:'): 45 | settings.mutex = (extractOptionValue(option) === 'true') 46 | break 47 | case option.startsWith('mode:'): 48 | settings.mode = extractOptionValue(option) 49 | break 50 | case option.startsWith('listmaxheight:'): 51 | settings.listmaxheight = extractOptionValue(option) 52 | break 53 | case option.startsWith('preload:'): 54 | settings.preload = extractOptionValue(option) 55 | break 56 | case option.startsWith('theme:'): 57 | settings.theme = extractOptionValue(option) 58 | break 59 | default: 60 | throwError(`Unrecognized tag argument(${index + 1}): ${value}`) 61 | } 62 | }) 63 | return settings 64 | } 65 | 66 | generate() { 67 | let settingLiteral = '' 68 | Object.entries(this.settings).forEach(([key, value]) => { 69 | settingLiteral += ` data-${key}="${value}"` 70 | }) 71 | return ` 72 |
` 75 | } 76 | } -------------------------------------------------------------------------------- /lib/view.es: -------------------------------------------------------------------------------- 1 | import {removeAll} from "../common/util" 2 | 3 | export default class PartialView { 4 | 5 | constructor(raw, info) { 6 | this.content = raw 7 | this.path = info.path || '' 8 | } 9 | 10 | isFullPage() { 11 | return this.content.includes('') 12 | } 13 | 14 | hasTagMarker(marker) { 15 | return this.content.includes(marker) 16 | } 17 | 18 | hasHeadTag() { 19 | return this.content.includes('') 20 | } 21 | 22 | assetAlreadyInjected(marker) { 23 | return this.content.includes(marker) 24 | } 25 | 26 | injectAsset(tag) { 27 | this.content = this.content.replace('', tag + '\n') 28 | } 29 | 30 | removeLiteral(text) { 31 | this.content = removeAll(this.content, text) 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-tag-aplayer", 3 | "version": "3.0.4", 4 | "description": "Embed aplayer in Hexo posts/pages", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "music", 11 | "hexo", 12 | "tag", 13 | "player", 14 | "aplayer" 15 | ], 16 | "author": "Grzhan", 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/grzhan/hexo-tag-aplayer.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/grzhan/hexo-tag-aplayer/issues" 24 | }, 25 | "homepage": "https://github.com/grzhan/hexo-tag-aplayer#readme", 26 | "dependencies": { 27 | "aplayer": "^1.10.0", 28 | "babel-polyfill": "^6.16.0", 29 | "hexo-fs": "^0.2.0", 30 | "hexo-log": "^0.2.0", 31 | "hexo-util": "^0.1.7", 32 | "meting": "^1.2.0" 33 | }, 34 | "devDependencies": { 35 | "babel-core": "^6.26.0", 36 | "babel-preset-es2015": "^6.18.0", 37 | "gulp": "^3.9.1", 38 | "gulp-babel": "^7.0.0", 39 | "gulp-clean": "^0.4.0", 40 | "gulp-plumber": "^1.2.0", 41 | "gulp-rename": "^1.2.2", 42 | "gulp-watch": "^5.0.0" 43 | } 44 | } 45 | --------------------------------------------------------------------------------