feat: Add support for dynamic thumbnails in Artplayer

The code changes introduce a new `art.thumbnails` property that allows for dynamic setting of thumbnails in Artplayer. This feature enhances the user experience by providing a visual preview of the video content.
This commit is contained in:
Harvey Zack committed 2024-08-15 22:50:21 +08:00
1 parent 5655db4b20
commit 52d9064c8b
10 files changed
+260 -110

No files matched your search

@@ -940,4 +940,28 @@ art.on('ready', () => {
];
}, 3000);
})
```
## `thumbnails`
- Type: `Setter/Getter`
- Parameter: `Object`
动态设置缩略图
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.thumbnails = {
url: '/assets/sample/thumbnails.png',
number: 60,
column: 10,
};
});
```
+7 -9
View File
@@ -81,15 +81,13 @@ export default class Control extends Component {
);
}
if (option.thumbnails.url && !option.isLive) {
this.add(
thumbnails({
name: 'thumbnails',
position: 'top',
index: 20,
}),
);
}
this.add(
thumbnails({
name: 'thumbnails',
position: 'top',
index: 20,
}),
);
this.add(
playAndPause({
+2 -66
View File
@@ -1,72 +1,8 @@
import { setStyle, isMobile } from '../utils';
export default function thumbnails(options) {
return (art) => ({
...options,
mounted: ($control) => {
const {
option,
events: { loadImg },
template: { $progress, $video },
} = art;
let timer = null;
let image = null;
let loading = false;
let isLoad = false;
function showThumbnails(posWidth) {
const { url, number, column, width, height } = option.thumbnails;
const width2 = width || image.naturalWidth / column;
const height2 = height || width2 / ($video.videoWidth / $video.videoHeight);
const perWidth = $progress.clientWidth / number;
const perIndex = Math.floor(posWidth / perWidth);
const yIndex = Math.ceil(perIndex / column) - 1;
const xIndex = perIndex % column || column - 1;
setStyle($control, 'backgroundImage', `url(${url})`);
setStyle($control, 'height', `${height2}px`);
setStyle($control, 'width', `${width2}px`);
setStyle($control, 'backgroundPosition', `-${xIndex * width2}px -${yIndex * height2}px`);
if (posWidth <= width2 / 2) {
setStyle($control, 'left', 0);
} else if (posWidth > $progress.clientWidth - width2 / 2) {
setStyle($control, 'left', `${$progress.clientWidth - width2}px`);
} else {
setStyle($control, 'left', `${posWidth - width2 / 2}px`);
}
}
art.on('setBar', async (type, percentage, event) => {
const isMobileDroging = type === 'played' && event && isMobile;
if (type === 'hover' || isMobileDroging) {
if (!loading) {
loading = true;
image = await loadImg(option.thumbnails.url);
isLoad = true;
}
if (!isLoad) return;
const width = $progress.clientWidth * percentage;
setStyle($control, 'display', 'flex');
if (width > 0 && width < $progress.clientWidth) {
showThumbnails(width);
} else {
if (!isMobile) {
setStyle($control, 'display', 'none');
}
}
if (isMobileDroging) {
clearTimeout(timer);
timer = setTimeout(() => {
setStyle($control, 'display', 'none');
}, 500);
}
}
});
mounted: () => {
this.art.thumbnails = art.options.thumbnails;
},
});
}
+2
View File
@@ -30,6 +30,7 @@ import stateMix from './stateMix';
import subtitleOffsetMix from './subtitleOffsetMix';
import airplayMix from './airplayMix';
import qualityMix from './qualityMix';
import thumbnailsMix from './thumbnailsMix';
import optionInit from './optionInit';
import eventInit from './eventInit';
@@ -67,6 +68,7 @@ export default class Player {
subtitleOffsetMix(art);
airplayMix(art);
qualityMix(art);
thumbnailsMix(art);
eventInit(art);
optionInit(art);
}
@@ -0,0 +1,93 @@
import { def, setStyle, isMobile } from '../utils';
export default function thumbnailsMix(art) {
const {
option,
events: { loadImg },
template: { $progress, $video },
} = art;
let timer = null;
let image = null;
let loading = false;
let isLoad = false;
function reset() {
clearTimeout(timer);
timer = null;
image = null;
loading = false;
isLoad = false;
}
function showThumbnails(posWidth) {
const $thumbnails = art.controls?.thumbnails;
if (!$thumbnails) return;
const { url, number, column, width, height } = option.thumbnails;
const width2 = width || image.naturalWidth / column;
const height2 = height || width2 / ($video.videoWidth / $video.videoHeight);
const perWidth = $progress.clientWidth / number;
const perIndex = Math.floor(posWidth / perWidth);
const yIndex = Math.ceil(perIndex / column) - 1;
const xIndex = perIndex % column || column - 1;
setStyle($thumbnails, 'backgroundImage', `url(${url})`);
setStyle($thumbnails, 'height', `${height2}px`);
setStyle($thumbnails, 'width', `${width2}px`);
setStyle($thumbnails, 'backgroundPosition', `-${xIndex * width2}px -${yIndex * height2}px`);
if (posWidth <= width2 / 2) {
setStyle($thumbnails, 'left', 0);
} else if (posWidth > $progress.clientWidth - width2 / 2) {
setStyle($thumbnails, 'left', `${$progress.clientWidth - width2}px`);
} else {
setStyle($thumbnails, 'left', `${posWidth - width2 / 2}px`);
}
}
art.on('setBar', async (type, percentage, event) => {
const $thumbnails = art.controls?.thumbnails;
if (!$thumbnails) return;
const isMobileDroging = type === 'played' && event && isMobile;
if (type === 'hover' || isMobileDroging) {
if (!loading) {
loading = true;
image = await loadImg(option.thumbnails.url);
isLoad = true;
}
if (!isLoad) return;
const width = $progress.clientWidth * percentage;
setStyle($thumbnails, 'display', 'flex');
if (width > 0 && width < $progress.clientWidth) {
showThumbnails(width);
} else {
if (!isMobile) {
setStyle($thumbnails, 'display', 'none');
}
}
if (isMobileDroging) {
clearTimeout(timer);
timer = setTimeout(() => {
setStyle($thumbnails, 'display', 'none');
}, 500);
}
}
});
def(art, 'thumbnails', {
get() {
return art.option.thumbnails;
},
set(thumbnails) {
if (thumbnails.url && !art.option.isLive) {
art.option.thumbnails = thumbnails;
reset();
}
},
});
}
+28 -26
View File
@@ -9,6 +9,33 @@ import Artplayer = require('./artplayer');
export type CustomType = 'flv' | 'm3u8' | 'hls' | 'ts' | 'mpd' | 'torrent' | (string & Record<never, never>);
export type Thumbnails = {
/**
* The thumbnail image url
*/
url: string;
/**
* The thumbnail item number
*/
number?: number;
/**
* The thumbnail column size
*/
column?: number;
/**
* The thumbnail width
*/
width?: number;
/**
* The thumbnail height
*/
height?: number;
};
export type Option = {
/**
* The player id
@@ -228,32 +255,7 @@ export type Option = {
/**
* Custom thumbnail
*/
thumbnails?: {
/**
* The thumbnail image url
*/
url: string;
/**
* The thumbnail item number
*/
number?: number;
/**
* The thumbnail column size
*/
column?: number;
/**
* The thumbnail width
*/
width?: number;
/**
* The thumbnail height
*/
height?: number;
};
thumbnails?: Thumbnails;
/**
* Custom subtitle option
+3 -1
View File
@@ -1,5 +1,5 @@
import { CssVar } from './cssVar';
import { CustomType } from './option';
import { CustomType, Thumbnails } from './option';
import { quality } from './quality';
export type AspectRatio = 'default' | '4:3' | '16:9' | (`${number}:${number}` & Record<never, never>);
@@ -63,6 +63,8 @@ export declare class Player {
set subtitleOffset(time: number);
set switch(url: string);
set quality(quality: quality[]);
get thumbnails(): Thumbnails;
set thumbnails(thumbnails: Thumbnails);
pause(): void;
play(): Promise<void>;
toggle(): void;