mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-15 17:16:16 -08:00
feat: batch video upscaling with folder upload, directory save, and result comparison (#1051)
* feat: support batch video upscaling with folder upload and directory save - Add multi-file selection and entire folder upload (webkitdirectory) - Support drag & drop of both files and folders via FileSystemEntry API - Add save directory picker using File System Access API (showDirectoryPicker) - Process videos sequentially with per-file progress and status tracking - Add right-side result list with click-to-compare functionality - Add per-video duration and total batch duration display - Add retry button for failed items with error detail display - Remove auto-play on comparison viewer load - Fix worker error handling: handle "error" command to reject stale promises - Fix event listener accumulation in video comparison (AbortController) - Fix XSS risk by escaping HTML in all innerHTML injections - Fix URL revocation timing in download to prevent large file truncation - Extract writeToSaveDirectory and truncateName helpers to reduce duplication - Add Usage Guide panel explaining single/multiple/folder upload options Files changed: docs/upscaler/index.html (+1110/-181) docs/upscaler/upscaler.js (+110) * fix: prevent auto-play and resource consumption during upscaling - Set preload="metadata" on preview and comparison video elements - Explicitly pause preview video after setting src in showVideoPreview - Remove preview video seek sync from batch processing progress callbacks (was causing continuous decode during upscaling, wasting GPU resources) - Remove preview video seek sync from retry progress callback - All videos now require manual play; no background resource usage * fix: improve upscaled video quality with higher bitrate and codec support - Raise base bitrate from 2.5 Mbps (25e5) to 8 Mbps (8e6) for standard and firefox processors, preventing quality loss when source videos have high bitrates (e.g. 720p @ 16.7 Mbps was producing 1440p @ 10 Mbps, smaller than the original file) - Double the bitrate for mediabunny processor (X.n1 * 2) to account for the 4x pixel increase from 2x upscaling - Add H.264 Level 5.1 (avc1.640033) and Level 5.2 (avc1.640034) codecs to the fallback lists, since Level 4.0 (avc1.640028, ~24.6 Mbps) cannot support the higher bitrates for 1440p+ outputs - Add latencyMode: "quality" to VideoEncoder configs for both standard and firefox processors, prioritizing encoding quality over latency (suitable for offline processing) * feat: add video data comparison panel for original vs upscaled metrics - Add a comparison data table below the file info bar showing 6 metrics: Resolution, File Size, Duration, Est. Bitrate, Pixels, Process Time - Display per-metric change percentage with color-coded indicators: green (↑) for improvements, red (↓) for regressions, gray for neutral - Estimated bitrate calculated from file size × 8 / video duration - Table renders after both original and upscaled video metadata loads - Auto-hide panel on reset * fix: make video data comparison panel full-width and responsive - Move comparison data table from inside the left column to the full-width area below the left/right layout, so it is no longer constrained by the comparison viewer column width - Remove table-fixed colgroup and overflow-x-auto that caused horizontal scrolling on smaller screens - Table now uses w-full auto-layout, displaying all 4 columns (Metric, Original, Upscaled, Change) without requiring horizontal scrolling --------- Co-authored-by: rionzhao <rionzhao@tencent.com>
This commit is contained in:
commit
0896841d23
538 files changed
+379715
No files matched your search
@@ -0,0 +1,48 @@
|
||||
// npm i artplayer-plugin-ads
|
||||
// import artplayerPluginAds from 'artplayer-plugin-ads';
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
autoSize: true,
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
plugins: [
|
||||
artplayerPluginAds({
|
||||
// html广告,假如是视频广告则忽略该值
|
||||
html: '<img src="/assets/sample/poster.jpg">',
|
||||
|
||||
// 视频广告的地址
|
||||
video: '/assets/sample/test1.mp4',
|
||||
|
||||
// 广告跳转网址,为空则不跳转
|
||||
url: 'http://artplayer.org',
|
||||
|
||||
// 必须观看的时长,期间不能被跳过,单位为秒
|
||||
// 当该值大于或等于totalDuration时,不能提前关闭广告
|
||||
// 当该值等于或小于0时,则随时都可以关闭广告
|
||||
playDuration: 5,
|
||||
|
||||
// 广告总时长,单位为秒
|
||||
totalDuration: 10,
|
||||
|
||||
// 多语言支持
|
||||
i18n: {
|
||||
close: '关闭广告',
|
||||
countdown: '%s秒',
|
||||
detail: '查看详情',
|
||||
canBeClosed: '%s秒后可关闭广告',
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
// 广告被点击
|
||||
art.on('artplayerPluginAds:click', (ads) => {
|
||||
console.info('广告被点击', ads)
|
||||
})
|
||||
|
||||
// 广告被跳过
|
||||
art.on('artplayerPluginAds:skip', (ads) => {
|
||||
console.info('广告被跳过', ads)
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
// npm i artplayer-plugin-ambilight
|
||||
// import artplayerPluginAmbilight from 'artplayer-plugin-ambilight';
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
autoSize: true,
|
||||
plugins: [
|
||||
artplayerPluginAmbilight({
|
||||
blur: '50px',
|
||||
opacity: 1,
|
||||
frequency: 10,
|
||||
duration: 0.3,
|
||||
}),
|
||||
],
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/steve-jobs.mp4',
|
||||
autoSize: true,
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
moreVideoAttr: {
|
||||
// crossOrigin: 'anonymous',
|
||||
},
|
||||
plugins: [
|
||||
artplayerPluginAsr({
|
||||
length: 2,
|
||||
interval: 40,
|
||||
sampleRate: 16000,
|
||||
autoHideTimeout: 10000,
|
||||
// Use your AI tool to convert pcm into subtitles
|
||||
onAudioChunk: ({ pcm }) => startAsr(pcm),
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
let ws = null
|
||||
let loading = false
|
||||
|
||||
function stopAsr() {
|
||||
try {
|
||||
ws.send(JSON.stringify({ type: 'end' }))
|
||||
ws.close()
|
||||
}
|
||||
catch {}
|
||||
ws = null
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function startAsr(buffer) {
|
||||
if (loading)
|
||||
return
|
||||
if (!ws) {
|
||||
loading = true
|
||||
const api = 'https://api.aimu.app/asr/tencent?engine_model_type=16k_en'
|
||||
const { url } = await (await fetch(api)).json()
|
||||
ws = new WebSocket(url)
|
||||
ws.binaryType = 'arraybuffer'
|
||||
ws.onmessage = (event) => {
|
||||
const { code, result, message } = JSON.parse(event.data)
|
||||
if (code === 0) {
|
||||
art.plugins.artplayerPluginAsr.append(result?.voice_text_str)
|
||||
}
|
||||
else {
|
||||
console.error(code, message)
|
||||
stopAsr()
|
||||
}
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
if (ws?.readyState === WebSocket.OPEN) {
|
||||
ws.send(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
art.on('destroy', stopAsr)
|
||||
@@ -0,0 +1,14 @@
|
||||
// npm i artplayer-plugin-audio-track
|
||||
// import artplayerPluginAudioTrack from 'artplayer-plugin-audio-track';
|
||||
|
||||
var art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/sprite-fight.mp4',
|
||||
plugins: [
|
||||
artplayerPluginAudioTrack({
|
||||
url: '/assets/sample/sprite-fight.aac',
|
||||
offset: 0,
|
||||
sync: 0.3,
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
// npm i artplayer-plugin-auto-thumbnail
|
||||
// import artplayerPluginAutoThumbnail from 'artplayer-plugin-auto-thumbnail';
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
plugins: [
|
||||
artplayerPluginAutoThumbnail({
|
||||
//
|
||||
}),
|
||||
],
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
// npm i artplayer-proxy-canvas
|
||||
// import artplayerProxyCanvas from 'artplayer-proxy-canvas';
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
poster: '/assets/sample/poster.jpg',
|
||||
volume: 0.5,
|
||||
autoplay: false,
|
||||
autoSize: false,
|
||||
screenshot: true,
|
||||
setting: true,
|
||||
loop: true,
|
||||
flip: true,
|
||||
pip: true,
|
||||
playbackRate: true,
|
||||
aspectRatio: true,
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
miniProgressBar: true,
|
||||
autoPlayback: true,
|
||||
autoOrientation: true,
|
||||
thumbnails: {
|
||||
url: '/assets/sample/thumbnails.png',
|
||||
number: 60,
|
||||
column: 10,
|
||||
scale: 0.85,
|
||||
},
|
||||
proxy: artplayerProxyCanvas(),
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
// npm i artplayer-plugin-chapter
|
||||
// import artplayerPluginChapter from 'artplayer-plugin-chapter';
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
autoSize: true,
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
miniProgressBar: true,
|
||||
autoOrientation: true,
|
||||
thumbnails: {
|
||||
url: '/assets/sample/thumbnails.png',
|
||||
number: 60,
|
||||
column: 10,
|
||||
},
|
||||
plugins: [
|
||||
artplayerPluginChapter({
|
||||
chapters: [
|
||||
{ start: 0, end: 18, title: 'One more chance' },
|
||||
{ start: 18, end: 36, title: '谁でもいいはずなのに' },
|
||||
{ start: 36, end: 54, title: '夏の想い出がまわる' },
|
||||
{ start: 54, end: 72, title: 'こんなとこにあるはずもないのに' },
|
||||
{ start: 72, end: Infinity, title: '终わり' },
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
// npm i artplayer-plugin-chromecast
|
||||
// import artplayerPluginChromecast from 'artplayer-plugin-chromecast';
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
plugins: [
|
||||
artplayerPluginChromecast({
|
||||
// sdk: '', // The URL of the Cast SDK
|
||||
// mimeType: '', // The MIME type of the media
|
||||
}),
|
||||
],
|
||||
})
|
||||
@@ -0,0 +1,54 @@
|
||||
// npm i artplayer-plugin-danmuku
|
||||
// import artplayerPluginDanmuku from 'artplayer-plugin-danmuku';
|
||||
// 使用文档 https://artplayer.org/document/plugin/danmuku.html
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
autoSize: true,
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
autoOrientation: true,
|
||||
plugins: [
|
||||
artplayerPluginDanmuku({
|
||||
danmuku: '/assets/sample/danmuku.xml',
|
||||
|
||||
// 以下为非必填
|
||||
speed: 5, // 弹幕持续时间,范围在[1 ~ 10]
|
||||
margin: [10, '25%'], // 弹幕上下边距,支持像素数字和百分比
|
||||
opacity: 1, // 弹幕透明度,范围在[0 ~ 1]
|
||||
color: '#FFFFFF', // 默认弹幕颜色,可以被单独弹幕项覆盖
|
||||
mode: 0, // 默认弹幕模式: 0: 滚动,1: 顶部,2: 底部
|
||||
modes: [0, 1, 2], // 弹幕可见的模式
|
||||
fontSize: 25, // 弹幕字体大小,支持像素数字和百分比
|
||||
antiOverlap: true, // 弹幕是否防重叠
|
||||
synchronousPlayback: false, // 是否同步播放速度
|
||||
mount: undefined, // 弹幕发射器挂载点, 默认为播放器控制栏中部
|
||||
heatmap: true, // 是否开启热力图
|
||||
width: 512, // 当播放器宽度小于此值时,弹幕发射器置于播放器底部
|
||||
points: [], // 热力图数据
|
||||
filter: danmu => danmu.text.length <= 100, // 弹幕载入前的过滤器
|
||||
beforeVisible: () => true, // 弹幕显示前的过滤器,返回 true 则可以发送
|
||||
visible: true, // 弹幕层是否可见
|
||||
emitter: true, // 是否开启弹幕发射器
|
||||
maxLength: 200, // 弹幕输入框最大长度, 范围在[1 ~ 1000]
|
||||
lockTime: 5, // 输入框锁定时间,范围在[1 ~ 60]
|
||||
theme: 'dark', // 弹幕主题,支持 dark 和 light,只在自定义挂载时生效
|
||||
OPACITY: {}, // 不透明度配置项
|
||||
FONT_SIZE: {}, // 弹幕字号配置项
|
||||
MARGIN: {}, // 显示区域配置项
|
||||
SPEED: {}, // 弹幕速度配置项
|
||||
COLOR: [], // 颜色列表配置项
|
||||
|
||||
// 手动发送弹幕前的过滤器,返回 true 则可以发送,可以做存库处理
|
||||
beforeEmit(danmu) {
|
||||
return new Promise((resolve) => {
|
||||
console.log(danmu)
|
||||
setTimeout(() => {
|
||||
resolve(true)
|
||||
}, 1000)
|
||||
})
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
// npm i artplayer-plugin-danmuku-mask
|
||||
// import artplayerPluginDanmukuMask from 'artplayer-plugin-danmuku-mask';
|
||||
|
||||
// npm i @mediapipe/selfie_segmentation
|
||||
// 把 node_modules/@mediapipe/selfie_segmentation 目录复制到你的项目下
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/steve-jobs.mp4',
|
||||
autoSize: true,
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
autoOrientation: true,
|
||||
plugins: [
|
||||
artplayerPluginDanmuku({
|
||||
danmuku: '/assets/sample/danmuku.xml',
|
||||
}),
|
||||
artplayerPluginDanmukuMask({
|
||||
solutionPath: '/assets/@mediapipe/selfie_segmentation',
|
||||
}),
|
||||
],
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
// npm i dashjs
|
||||
// npm i artplayer-plugin-dash-control
|
||||
|
||||
// import dashjs from 'dashjs';
|
||||
// import artplayerPluginDashControl from 'artplayer-plugin-dash-control';
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: 'https://media.axprod.net/TestVectors/v7-Clear/Manifest_1080p.mpd',
|
||||
setting: true,
|
||||
plugins: [
|
||||
artplayerPluginDashControl({
|
||||
quality: {
|
||||
// Show qualitys in control
|
||||
control: true,
|
||||
// Show qualitys in setting
|
||||
setting: true,
|
||||
// Get the quality name from level
|
||||
getName: level => `${level.height}P`,
|
||||
// I18n
|
||||
title: 'Quality',
|
||||
auto: 'Auto',
|
||||
},
|
||||
audio: {
|
||||
// Show audios in control
|
||||
control: true,
|
||||
// Show audios in setting
|
||||
setting: true,
|
||||
// Get the audio name from track
|
||||
getName: track => track.lang.toUpperCase(),
|
||||
// I18n
|
||||
title: 'Audio',
|
||||
auto: 'Auto',
|
||||
},
|
||||
}),
|
||||
],
|
||||
customType: {
|
||||
mpd: function playMpd(video, url, art) {
|
||||
if (dashjs.supportsMediaSource()) {
|
||||
if (art.dash)
|
||||
art.dash.destroy()
|
||||
const dash = dashjs.MediaPlayer().create()
|
||||
dash.initialize(video, url, art.option.autoplay)
|
||||
art.dash = dash
|
||||
art.on('destroy', () => dash.destroy())
|
||||
}
|
||||
else {
|
||||
art.notice.show = 'Unsupported playback format: mpd'
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
// npm i dashjs
|
||||
// import dashjs from 'dashjs';
|
||||
|
||||
function playMpd(video, url, art) {
|
||||
if (dashjs.supportsMediaSource()) {
|
||||
if (art.dash)
|
||||
art.dash.destroy()
|
||||
const dash = dashjs.MediaPlayer().create()
|
||||
dash.initialize(video, url, art.option.autoplay)
|
||||
art.dash = dash
|
||||
art.on('destroy', () => dash.destroy())
|
||||
}
|
||||
else {
|
||||
art.notice.show = 'Unsupported playback format: mpd'
|
||||
}
|
||||
}
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd',
|
||||
type: 'mpd',
|
||||
customType: {
|
||||
mpd: playMpd,
|
||||
},
|
||||
})
|
||||
|
||||
art.on('ready', () => {
|
||||
console.info(art.dash)
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
// npm i artplayer-plugin-document-pip
|
||||
// import artplayerPluginDocumentPip from 'artplayer-plugin-document-pip';
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
plugins: [
|
||||
artplayerPluginDocumentPip({
|
||||
width: 480,
|
||||
height: 270,
|
||||
fallbackToVideoPiP: true,
|
||||
placeholder: `Playing in Document Picture-in-Picture`,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
art.on('document-pip', (state) => {
|
||||
console.log('Document Picture-in-Picture', state)
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
// npm i flv.js
|
||||
// import flvjs from 'flv.js';
|
||||
|
||||
function playFlv(video, url, art) {
|
||||
if (flvjs.isSupported()) {
|
||||
if (art.flv)
|
||||
art.flv.destroy()
|
||||
const flv = flvjs.createPlayer({ type: 'flv', url })
|
||||
flv.attachMediaElement(video)
|
||||
flv.load()
|
||||
art.flv = flv
|
||||
art.on('destroy', () => flv.destroy())
|
||||
}
|
||||
else {
|
||||
art.notice.show = 'Unsupported playback format: flv'
|
||||
}
|
||||
}
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.flv',
|
||||
type: 'flv',
|
||||
customType: {
|
||||
flv: playFlv,
|
||||
},
|
||||
})
|
||||
|
||||
art.on('ready', () => {
|
||||
console.info(art.flv)
|
||||
})
|
||||
@@ -0,0 +1,56 @@
|
||||
// npm i hls.js
|
||||
// npm i artplayer-plugin-hls-control
|
||||
|
||||
// import Hls from 'hls.js';
|
||||
// import artplayerPluginHlsControl from 'artplayer-plugin-hls-control';
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: 'https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8',
|
||||
setting: true,
|
||||
plugins: [
|
||||
artplayerPluginHlsControl({
|
||||
quality: {
|
||||
// Show qualitys in control
|
||||
control: true,
|
||||
// Show qualitys in setting
|
||||
setting: true,
|
||||
// Get the quality name from level
|
||||
getName: level => `${level.height}P`,
|
||||
// I18n
|
||||
title: 'Quality',
|
||||
auto: 'Auto',
|
||||
},
|
||||
audio: {
|
||||
// Show audios in control
|
||||
control: true,
|
||||
// Show audios in setting
|
||||
setting: true,
|
||||
// Get the audio name from track
|
||||
getName: track => track.name,
|
||||
// I18n
|
||||
title: 'Audio',
|
||||
auto: 'Auto',
|
||||
},
|
||||
}),
|
||||
],
|
||||
customType: {
|
||||
m3u8: function playM3u8(video, url, art) {
|
||||
if (Hls.isSupported()) {
|
||||
if (art.hls)
|
||||
art.hls.destroy()
|
||||
const hls = new Hls()
|
||||
hls.loadSource(url)
|
||||
hls.attachMedia(video)
|
||||
art.hls = hls
|
||||
art.on('destroy', () => hls.destroy())
|
||||
}
|
||||
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
video.src = url
|
||||
}
|
||||
else {
|
||||
art.notice.show = 'Unsupported playback format: m3u8'
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,33 @@
|
||||
// npm i hls.js
|
||||
// import Hls from 'hls.js';
|
||||
|
||||
function playM3u8(video, url, art) {
|
||||
if (Hls.isSupported()) {
|
||||
if (art.hls)
|
||||
art.hls.destroy()
|
||||
const hls = new Hls()
|
||||
hls.loadSource(url)
|
||||
hls.attachMedia(video)
|
||||
art.hls = hls
|
||||
art.on('destroy', () => hls.destroy())
|
||||
}
|
||||
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
video.src = url
|
||||
}
|
||||
else {
|
||||
art.notice.show = 'Unsupported playback format: m3u8'
|
||||
}
|
||||
}
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8',
|
||||
type: 'm3u8',
|
||||
customType: {
|
||||
m3u8: playM3u8,
|
||||
},
|
||||
})
|
||||
|
||||
art.on('ready', () => {
|
||||
console.info(art.hls)
|
||||
})
|
||||
@@ -0,0 +1,47 @@
|
||||
// npm i artplayer-tool-iframe
|
||||
// import ArtplayerToolIframe from 'artplayer-tool-iframe';
|
||||
|
||||
const $iframe = document.createElement('iframe')
|
||||
$iframe.allowFullscreen = true
|
||||
$iframe.width = '100%'
|
||||
$iframe.height = '100%'
|
||||
|
||||
const $container = document.querySelector('.artplayer-app')
|
||||
$container.innerHTML = ''
|
||||
$container.appendChild($iframe)
|
||||
|
||||
const iframe = new ArtplayerToolIframe({
|
||||
iframe: $iframe,
|
||||
url: '/iframe.html',
|
||||
})
|
||||
|
||||
iframe.message(({ type, data }) => {
|
||||
switch (type) {
|
||||
case 'fullscreenWeb':
|
||||
if (data) {
|
||||
$iframe.classList.add('fullscreenWeb')
|
||||
}
|
||||
else {
|
||||
$iframe.classList.remove('fullscreenWeb')
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
iframe.commit(() => {
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
})
|
||||
|
||||
art.on('fullscreenWeb', (state) => {
|
||||
ArtplayerToolIframe.postMessage({
|
||||
type: 'fullscreenWeb',
|
||||
data: state,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,190 @@
|
||||
var art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
poster: '/assets/sample/poster.jpg',
|
||||
volume: 0.5,
|
||||
isLive: false,
|
||||
muted: false,
|
||||
autoplay: false,
|
||||
pip: true,
|
||||
autoSize: true,
|
||||
autoMini: true,
|
||||
screenshot: true,
|
||||
setting: true,
|
||||
loop: true,
|
||||
flip: true,
|
||||
playbackRate: true,
|
||||
aspectRatio: true,
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
subtitleOffset: true,
|
||||
miniProgressBar: true,
|
||||
mutex: true,
|
||||
backdrop: true,
|
||||
playsInline: true,
|
||||
autoPlayback: true,
|
||||
airplay: true,
|
||||
theme: '#23ade5',
|
||||
lang: navigator.language.toLowerCase(),
|
||||
moreVideoAttr: {
|
||||
crossOrigin: 'anonymous',
|
||||
},
|
||||
settings: [
|
||||
{
|
||||
width: 200,
|
||||
html: 'Subtitle',
|
||||
tooltip: 'Bilingual',
|
||||
icon: '<img width="22" height="22" src="/assets/img/subtitle.svg">',
|
||||
selector: [
|
||||
{
|
||||
html: 'Display',
|
||||
tooltip: 'Show',
|
||||
switch: true,
|
||||
onSwitch(item) {
|
||||
item.tooltip = item.switch ? 'Hide' : 'Show'
|
||||
art.subtitle.show = !item.switch
|
||||
return !item.switch
|
||||
},
|
||||
},
|
||||
{
|
||||
default: true,
|
||||
html: 'Bilingual',
|
||||
url: '/assets/sample/subtitle.srt',
|
||||
},
|
||||
{
|
||||
html: 'Chinese',
|
||||
url: '/assets/sample/subtitle.cn.srt',
|
||||
},
|
||||
{
|
||||
html: 'Japanese',
|
||||
url: '/assets/sample/subtitle.jp.srt',
|
||||
},
|
||||
],
|
||||
onSelect(item) {
|
||||
art.subtitle.switch(item.url, {
|
||||
name: item.html,
|
||||
})
|
||||
return item.html
|
||||
},
|
||||
},
|
||||
{
|
||||
html: 'Switcher',
|
||||
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
|
||||
tooltip: 'OFF',
|
||||
switch: false,
|
||||
onSwitch(item) {
|
||||
item.tooltip = item.switch ? 'OFF' : 'ON'
|
||||
console.info('You clicked on the custom switch', item.switch)
|
||||
return !item.switch
|
||||
},
|
||||
},
|
||||
{
|
||||
html: 'Slider',
|
||||
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
|
||||
tooltip: '5x',
|
||||
range: [5, 1, 10, 0.1],
|
||||
onRange(item) {
|
||||
return `${item.range[0]}x`
|
||||
},
|
||||
},
|
||||
{
|
||||
html: 'Button',
|
||||
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
|
||||
tooltip: 'tooltip',
|
||||
onClick() {
|
||||
return 'Button clicked'
|
||||
},
|
||||
},
|
||||
],
|
||||
contextmenu: [
|
||||
{
|
||||
html: 'Custom menu',
|
||||
click(contextmenu) {
|
||||
console.info('You clicked on the custom menu')
|
||||
contextmenu.show = false
|
||||
},
|
||||
},
|
||||
],
|
||||
layers: [
|
||||
{
|
||||
html: '<img width="100" src="/assets/sample/layer.png">',
|
||||
click() {
|
||||
window.open('https://aimu.app')
|
||||
console.info('You clicked on the custom layer')
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '20px',
|
||||
right: '20px',
|
||||
opacity: '.9',
|
||||
},
|
||||
},
|
||||
],
|
||||
quality: [
|
||||
{
|
||||
default: true,
|
||||
html: 'SD 480P',
|
||||
url: '/assets/sample/video.mp4?q=480',
|
||||
},
|
||||
{
|
||||
html: 'HD 720P',
|
||||
url: '/assets/sample/video.mp4?q=720',
|
||||
},
|
||||
],
|
||||
thumbnails: {
|
||||
url: '/assets/sample/thumbnails.png',
|
||||
number: 60,
|
||||
column: 10,
|
||||
scale: 0.85,
|
||||
},
|
||||
subtitle: {
|
||||
url: '/assets/sample/subtitle.srt',
|
||||
type: 'srt',
|
||||
style: {
|
||||
color: '#fe9200',
|
||||
fontSize: '20px',
|
||||
},
|
||||
encoding: 'utf-8',
|
||||
},
|
||||
highlight: [
|
||||
{
|
||||
time: 15,
|
||||
text: 'One more chance',
|
||||
},
|
||||
{
|
||||
time: 30,
|
||||
text: '谁でもいいはずなのに',
|
||||
},
|
||||
{
|
||||
time: 45,
|
||||
text: '夏の想い出がまわる',
|
||||
},
|
||||
{
|
||||
time: 60,
|
||||
text: 'こんなとこにあるはずもないのに',
|
||||
},
|
||||
{
|
||||
time: 75,
|
||||
text: '终わり',
|
||||
},
|
||||
],
|
||||
controls: [
|
||||
{
|
||||
position: 'right',
|
||||
html: 'Control',
|
||||
index: 1,
|
||||
tooltip: 'Control Tooltip',
|
||||
style: {
|
||||
marginRight: '20px',
|
||||
},
|
||||
click() {
|
||||
console.info('You clicked on the custom control')
|
||||
},
|
||||
},
|
||||
],
|
||||
icons: {
|
||||
loading: '<img src="/assets/img/ploading.gif">',
|
||||
state: '<img width="150" height="150" src="/assets/img/state.svg">',
|
||||
indicator: '<img width="16" height="16" src="/assets/img/indicator.svg">',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
// https://github.com/ThaUnknown/jassub
|
||||
// npm i artplayer-plugin-jassub
|
||||
// import artplayerPluginJassub from 'artplayer-plugin-jassub';
|
||||
|
||||
var art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/jassub/FGOBD.mp4',
|
||||
autoSize: true,
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
plugins: [
|
||||
artplayerPluginJassub({
|
||||
subUrl: '/assets/jassub/FGOBD.ass',
|
||||
workerUrl: '/assets/jassub/jassub-worker.js',
|
||||
wasmUrl: '/assets/jassub/jassub-worker.wasm',
|
||||
modernWasmUrl: '/assets/jassub/jassub-worker-modern.wasm',
|
||||
availableFonts: {
|
||||
'liberation sans': '/assets/jassub/default.woff2'
|
||||
},
|
||||
fonts: [
|
||||
'/assets/jassub/fonts/Averia Sans Libre Light.ttf',
|
||||
'/assets/jassub/fonts/Averia Serif Simple Light.ttf',
|
||||
'/assets/jassub/fonts/Gramond.ttf'
|
||||
],
|
||||
timeOffset: -0.041
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
// npm i artplayer-proxy-mediabunny
|
||||
// import artplayerProxyMediabunny from 'artplayer-proxy-mediabunny';
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8',
|
||||
autoSize: true,
|
||||
setting: true,
|
||||
loop: true,
|
||||
flip: true,
|
||||
playbackRate: true,
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
miniProgressBar: true,
|
||||
autoPlayback: true,
|
||||
autoOrientation: true,
|
||||
proxy: artplayerProxyMediabunny({
|
||||
m3u8: {
|
||||
quality: {
|
||||
control: true,
|
||||
setting: true,
|
||||
getName: level => level.height ? `${level.height}P` : level.name,
|
||||
title: 'Quality',
|
||||
auto: 'Auto',
|
||||
},
|
||||
audio: {
|
||||
control: true,
|
||||
setting: true,
|
||||
getName: track => track.name || track.language,
|
||||
title: 'Audio',
|
||||
auto: 'Auto',
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
@@ -0,0 +1,98 @@
|
||||
var art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
poster: '/assets/sample/poster.jpg',
|
||||
autoSize: true,
|
||||
loop: true,
|
||||
mutex: true,
|
||||
setting: true,
|
||||
flip: true,
|
||||
lock: true,
|
||||
fastForward: true,
|
||||
playbackRate: true,
|
||||
aspectRatio: true,
|
||||
theme: '#ff0057',
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
miniProgressBar: true,
|
||||
autoOrientation: true,
|
||||
airplay: true,
|
||||
moreVideoAttr: {
|
||||
'x5-video-player-type': 'h5',
|
||||
'x5-video-player-fullscreen': false,
|
||||
'x5-video-orientation': 'portraint',
|
||||
'preload': 'metadata',
|
||||
},
|
||||
thumbnails: {
|
||||
url: '/assets/sample/thumbnails.png',
|
||||
number: 60,
|
||||
column: 10,
|
||||
scale: 0.6,
|
||||
},
|
||||
subtitle: {
|
||||
name: '中日双语',
|
||||
url: '/assets/sample/subtitle.srt',
|
||||
style: {
|
||||
color: '#48aff0',
|
||||
fontSize: '16px',
|
||||
},
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
html: `<img width="50" src="/assets/sample/layer.png">`,
|
||||
click() {
|
||||
art.notice.show = '你点击了自定义层'
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '10px',
|
||||
right: '10px',
|
||||
opacity: '.9',
|
||||
},
|
||||
},
|
||||
],
|
||||
icons: {
|
||||
loading: '<img src="/assets/img/ploading.gif">',
|
||||
state: '<img width="150" height="150" src="/assets/img/state.svg">',
|
||||
indicator: '<img width="16" height="16" src="/assets/img/indicator.svg">',
|
||||
},
|
||||
settings: [
|
||||
{
|
||||
width: 200,
|
||||
html: '切换字幕',
|
||||
tooltip: '双语',
|
||||
icon: '<img width="22" height="22" src="/assets/img/subtitle.svg">',
|
||||
selector: [
|
||||
{
|
||||
html: '开关',
|
||||
switch: true,
|
||||
tooltip: '显示',
|
||||
onSwitch(item) {
|
||||
item.tooltip = item.switch ? '隐藏' : '显示'
|
||||
art.subtitle.show = !item.switch
|
||||
return !item.switch
|
||||
},
|
||||
},
|
||||
{
|
||||
default: true,
|
||||
html: '双语',
|
||||
url: '/assets/sample/subtitle.srt',
|
||||
},
|
||||
{
|
||||
html: '中文',
|
||||
url: '/assets/sample/subtitle.cn.srt',
|
||||
},
|
||||
{
|
||||
html: '日文',
|
||||
url: '/assets/sample/subtitle.jp.srt',
|
||||
},
|
||||
],
|
||||
onSelect(item) {
|
||||
art.subtitle.switch(item.url, {
|
||||
name: item.html,
|
||||
})
|
||||
return item.html
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
// npm i mpegts
|
||||
// import mpegts from 'mpegts';
|
||||
|
||||
function playFlv(video, url, art) {
|
||||
if (mpegts.isSupported()) {
|
||||
if (art.flv)
|
||||
art.flv.destroy()
|
||||
|
||||
const flv = mpegts.createPlayer({
|
||||
type: 'flv',
|
||||
url,
|
||||
})
|
||||
flv.attachMediaElement(video)
|
||||
flv.load()
|
||||
flv.play()
|
||||
|
||||
art.flv = flv
|
||||
art.on('destroy', () => flv.destroy())
|
||||
}
|
||||
else {
|
||||
art.notice.show = 'Unsupported playback format: flv'
|
||||
}
|
||||
}
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.flv',
|
||||
type: 'flv',
|
||||
customType: {
|
||||
flv: playFlv,
|
||||
},
|
||||
})
|
||||
|
||||
art.on('ready', () => {
|
||||
console.info(art.flv)
|
||||
})
|
||||
@@ -0,0 +1,113 @@
|
||||
// npm i artplayer-plugin-multiple-subtitles
|
||||
// import artplayerPluginMultipleSubtitles from 'artplayer-plugin-multiple-subtitles';
|
||||
|
||||
var art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
setting: true,
|
||||
plugins: [
|
||||
artplayerPluginMultipleSubtitles({
|
||||
subtitles: [
|
||||
{
|
||||
name: 'chinese',
|
||||
url: '/assets/sample/subtitle.cn.srt',
|
||||
},
|
||||
{
|
||||
name: 'japanese',
|
||||
url: '/assets/sample/subtitle.jp.srt',
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
settings: [
|
||||
{
|
||||
width: 200,
|
||||
html: 'Subtitle',
|
||||
tooltip: 'Double',
|
||||
icon: '<img width="22" height="22" src="/assets/img/subtitle.svg">',
|
||||
selector: [
|
||||
{
|
||||
html: 'Display',
|
||||
tooltip: 'Show',
|
||||
switch: true,
|
||||
onSwitch(item) {
|
||||
item.tooltip = item.switch ? 'Hide' : 'Show'
|
||||
// 显示/隐藏字幕
|
||||
// Show/hide subtitles
|
||||
art.subtitle.show = !item.switch
|
||||
return !item.switch
|
||||
},
|
||||
},
|
||||
{
|
||||
html: 'Reverse',
|
||||
tooltip: 'Off',
|
||||
switch: false,
|
||||
onSwitch(item) {
|
||||
item.tooltip = item.switch ? 'Off' : 'On'
|
||||
// 修改字幕顺序
|
||||
// Change the order of subtitles
|
||||
if (item.switch) {
|
||||
art.plugins.multipleSubtitles.tracks(['chinese', 'japanese'])
|
||||
}
|
||||
else {
|
||||
art.plugins.multipleSubtitles.tracks(['japanese', 'chinese'])
|
||||
}
|
||||
return !item.switch
|
||||
},
|
||||
},
|
||||
{
|
||||
default: true,
|
||||
html: 'Double',
|
||||
name: 'double',
|
||||
},
|
||||
{
|
||||
html: 'Chinese',
|
||||
name: 'chinese',
|
||||
},
|
||||
{
|
||||
html: 'Japanese',
|
||||
name: 'japanese',
|
||||
},
|
||||
],
|
||||
onSelect(item) {
|
||||
if (item.name === 'double') {
|
||||
// 重置字幕
|
||||
// Reset subtitles
|
||||
art.plugins.multipleSubtitles.reset()
|
||||
}
|
||||
else {
|
||||
// 显示单个字幕
|
||||
// Show single subtitle
|
||||
art.plugins.multipleSubtitles.tracks([item.name])
|
||||
}
|
||||
return item.html
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// 自定义你自己的样式,请勿复制以下代码
|
||||
// Customize your own style, please do not copy the following code
|
||||
|
||||
const style = `
|
||||
.art-subtitle-chinese {
|
||||
color: red;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.art-subtitle-japanese {
|
||||
color: yellow;
|
||||
font-size: 12px;
|
||||
}
|
||||
`
|
||||
|
||||
const $style = document.getElementById('artplayer-subtitle-style')
|
||||
if ($style) {
|
||||
$style.textContent = style
|
||||
}
|
||||
else {
|
||||
const $style = document.createElement('style')
|
||||
$style.id = 'artplayer-subtitle-style'
|
||||
$style.textContent = style
|
||||
document.head.appendChild($style)
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
var art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
setting: true,
|
||||
flip: true,
|
||||
playbackRate: true,
|
||||
aspectRatio: true,
|
||||
subtitleOffset: true,
|
||||
settings: [
|
||||
{
|
||||
width: 200,
|
||||
html: 'Subtitle',
|
||||
name: 'subtitle',
|
||||
tooltip: 'Bilingual',
|
||||
icon: '<img width="22" height="22" src="/assets/img/subtitle.svg">',
|
||||
selector: [
|
||||
{
|
||||
html: 'Display',
|
||||
tooltip: 'Show',
|
||||
switch: true,
|
||||
onSwitch(item) {
|
||||
item.tooltip = item.switch ? 'Hide' : 'Show'
|
||||
art.subtitle.show = !item.switch
|
||||
return !item.switch
|
||||
},
|
||||
},
|
||||
{
|
||||
default: true,
|
||||
html: 'Bilingual',
|
||||
url: '/assets/sample/subtitle.srt',
|
||||
},
|
||||
{
|
||||
html: 'Chinese',
|
||||
url: '/assets/sample/subtitle.cn.srt',
|
||||
},
|
||||
{
|
||||
html: 'Japanese',
|
||||
url: '/assets/sample/subtitle.jp.srt',
|
||||
},
|
||||
],
|
||||
onSelect(item) {
|
||||
art.subtitle.switch(item.url, {
|
||||
name: item.html,
|
||||
})
|
||||
return item.html
|
||||
},
|
||||
mounted(...args) {
|
||||
console.info(args)
|
||||
},
|
||||
},
|
||||
{
|
||||
html: 'Switcher',
|
||||
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
|
||||
tooltip: 'OFF',
|
||||
switch: false,
|
||||
onSwitch(item) {
|
||||
item.tooltip = item.switch ? 'OFF' : 'ON'
|
||||
console.info('You clicked on the custom switch', item.switch)
|
||||
return !item.switch
|
||||
},
|
||||
mounted(...args) {
|
||||
console.info(args)
|
||||
},
|
||||
},
|
||||
{
|
||||
html: 'Slider',
|
||||
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
|
||||
tooltip: '5x',
|
||||
range: [5, 1, 10, 0.1],
|
||||
onRange(item) {
|
||||
return `${item.range[0]}x`
|
||||
},
|
||||
mounted(...args) {
|
||||
console.info(args)
|
||||
},
|
||||
},
|
||||
],
|
||||
}, async () => {
|
||||
const { sleep } = Artplayer.utils
|
||||
art.setting.show = true
|
||||
console.log(art.setting.builtin)
|
||||
console.log(art.setting.find('aspect-ratio'))
|
||||
console.log(art.setting.find('aspect-ratio2'))
|
||||
await sleep(1000)
|
||||
art.setting.resize()
|
||||
await sleep(1000)
|
||||
art.setting.inactivate(art.setting.find('subtitle'))
|
||||
art.setting.remove('aspect-ratio')
|
||||
try {
|
||||
art.setting.remove('aspect-ratio2')
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error.message)
|
||||
}
|
||||
await sleep(1000)
|
||||
art.setting.update({
|
||||
name: 'subtitle-offset',
|
||||
html: 'new offset',
|
||||
range: [5, -11, 11, 1],
|
||||
})
|
||||
await sleep(1000)
|
||||
art.setting.find('subtitle-offset').range = [0, -0, 10, 1]
|
||||
await sleep(1000)
|
||||
art.setting.update({
|
||||
name: 'subtitle-offset2',
|
||||
html: 'new offset 2',
|
||||
range: [5, -11, 11, 1],
|
||||
onChange(item) {
|
||||
return `${item.range[0]}s`
|
||||
},
|
||||
})
|
||||
await sleep(1000)
|
||||
art.setting.update({
|
||||
name: 'flip',
|
||||
html: 'new flip',
|
||||
tooltip: 'OFF',
|
||||
switch: false,
|
||||
})
|
||||
await sleep(1000)
|
||||
art.setting.find('flip').switch = true
|
||||
await sleep(1000)
|
||||
art.setting.update({
|
||||
name: 'flip2',
|
||||
html: 'new flip2',
|
||||
tooltip: 'OFF',
|
||||
switch: true,
|
||||
})
|
||||
await sleep(1000)
|
||||
try {
|
||||
art.setting.add({
|
||||
name: 'flip2',
|
||||
html: 'new flip2',
|
||||
tooltip: 'OFF',
|
||||
switch: true,
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error.message)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
// npm i artplayer-plugin-thumbnail
|
||||
// import artplayerPluginThumbnail from 'artplayer-plugin-thumbnail';
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
plugins: [
|
||||
artplayerPluginThumbnail({
|
||||
width: 160,
|
||||
number: 100,
|
||||
scale: 1,
|
||||
}),
|
||||
],
|
||||
})
|
||||
@@ -0,0 +1,69 @@
|
||||
if (window.lastThumbnail) {
|
||||
window.lastThumbnail.destroy();
|
||||
}
|
||||
|
||||
var $popups = document.querySelector('.popups');
|
||||
var $popinner = document.querySelector('.popinner');
|
||||
var $artplayer = document.querySelector('.artplayer-app');
|
||||
|
||||
$artplayer.innerHTML = 'Drop video file here or click to upload.';
|
||||
var thumbnail = new ArtplayerToolThumbnail({
|
||||
fileInput: $artplayer,
|
||||
number: 60, // 数量
|
||||
width: 160, // 宽度
|
||||
column: 10, // 列数
|
||||
begin: 0, // 开始
|
||||
end: NaN, // 结束
|
||||
});
|
||||
|
||||
window.lastThumbnail = thumbnail;
|
||||
|
||||
thumbnail.on('file', function (file) {
|
||||
console.log('Read video successfully: ' + file.name);
|
||||
});
|
||||
|
||||
thumbnail.on('video', function (video) {
|
||||
console.log('Video size: ' + video.videoWidth + ' x ' + video.videoHeight);
|
||||
console.log('Video duration: ' + video.duration + 's');
|
||||
thumbnail.start();
|
||||
});
|
||||
|
||||
thumbnail.on('canvas', function (canvas) {
|
||||
console.log('Build canvas successfully');
|
||||
console.log('Canvas size: ' + canvas.width + ' x ' + canvas.height);
|
||||
console.log('Preview density: ' + thumbnail.density + ' p/s');
|
||||
});
|
||||
|
||||
thumbnail.on('update', function (url, percentage) {
|
||||
console.log('Processing: ' + Math.floor(percentage.toFixed(2) * 100) + '%');
|
||||
$popups.style.display = 'flex';
|
||||
$popinner.style.backgroundImage = 'url(' + url + ')';
|
||||
});
|
||||
|
||||
thumbnail.on('download', function (name) {
|
||||
console.log('Start download preview: ' + name);
|
||||
});
|
||||
|
||||
thumbnail.on('done', function () {
|
||||
$popups.style.display = 'none';
|
||||
thumbnail.download();
|
||||
console.log('Build preview image complete');
|
||||
|
||||
[...Artplayer.instances].forEach(function (art) {
|
||||
art.destroy(true);
|
||||
});
|
||||
|
||||
new Artplayer({
|
||||
container: $artplayer,
|
||||
url: thumbnail.videoUrl,
|
||||
autoSize: true,
|
||||
poster: thumbnail.thumbnailUrl,
|
||||
thumbnails: {
|
||||
url: thumbnail.thumbnailUrl,
|
||||
number: thumbnail.option.number,
|
||||
column: thumbnail.option.column,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Build player complete');
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
// Depends on:
|
||||
// https://glomex.github.io/vast-ima-player/
|
||||
// https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side
|
||||
|
||||
// Google's IMA SDK are blocked by your Ad blocker.
|
||||
// Please Turn Off Your Ad Blocker.
|
||||
|
||||
// npm i artplayer-plugin-vast
|
||||
// import artplayerPluginVast from 'artplayer-plugin-vast';
|
||||
|
||||
var art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/video.mp4',
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
plugins: [
|
||||
artplayerPluginVast(({ playUrl, imaPlayer, ima }) => {
|
||||
// Play the ad when the video is played
|
||||
art.once('play', () => {
|
||||
playUrl('https://artplayer.org/assets/vast/linear-ad.xml')
|
||||
})
|
||||
}),
|
||||
],
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
// npm i artplayer-plugin-vtt-thumbnail
|
||||
// import artplayerPluginVttThumbnail from 'artplayer-plugin-vtt-thumbnail';
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: '/assets/sample/bbb-video.mp4',
|
||||
plugins: [
|
||||
artplayerPluginVttThumbnail({
|
||||
vtt: '/assets/sample/bbb-thumbnails.vtt',
|
||||
}),
|
||||
],
|
||||
})
|
||||
@@ -0,0 +1,38 @@
|
||||
// npm i webtorrent
|
||||
// import WebTorrent from 'webtorrent';
|
||||
|
||||
async function playTorrent(video, url, art) {
|
||||
if (WebTorrent.WEBRTC_SUPPORT) {
|
||||
if (art.torrent)
|
||||
art.torrent.destroy()
|
||||
art.torrent = new WebTorrent()
|
||||
|
||||
await navigator.serviceWorker.register('/webtorrent.sw.min.js')
|
||||
art.torrent.loadWorker(navigator.serviceWorker.controller)
|
||||
|
||||
art.torrent.add(url, (torrent) => {
|
||||
const file = torrent.files.find((file) => {
|
||||
return file.name.endsWith('.mp4')
|
||||
})
|
||||
file.streamTo(video)
|
||||
})
|
||||
|
||||
art.on('destroy', () => art.torrent.destroy())
|
||||
}
|
||||
else {
|
||||
art.notice.show = 'Unsupported playback format: torrent'
|
||||
}
|
||||
}
|
||||
|
||||
const art = new Artplayer({
|
||||
container: '.artplayer-app',
|
||||
url: 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent',
|
||||
type: 'torrent',
|
||||
customType: {
|
||||
torrent: playTorrent,
|
||||
},
|
||||
})
|
||||
|
||||
art.on('ready', () => {
|
||||
console.info(art.torrent)
|
||||
})
|
||||
Reference in new issue
Block a user