zhw2590582 / ArtPlayer

:art: ArtPlayer.js is a modern and full featured HTML5 video player
https://artplayer.org
MIT License
2.64k stars 275 forks source link

autoskip some portion of video #844

Closed itzzzme closed 1 month ago

itzzzme commented 1 month ago

I was trying to know that how I can autoskip specific fragment of my video

zhw2590582 commented 1 month ago

Yes, I wrote an artplayerPluginSkip plugin: demo%20%7B%0A%09%09%09throw%20new%20TypeError(%27Option%20must%20be%20an%20array%20of%20time%20ranges%27)%3B%0A%09%09%7D%0A%0A%09%09ranges.forEach((range%2C%20index)%20%3D%3E%20%7B%0A%09%09%09if%20(!Array.isArray(range)%20%7C%7C%20range.length%20!%3D%3D%202)%20%7B%0A%09%09%09%09throw%20new%20TypeError(%60Range%20at%20index%20%24%7Bindex%7D%20must%20be%20an%20array%20of%20two%20numbers%60)%3B%0A%09%09%09%7D%0A%0A%09%09%09const%20%5Bstart%2C%20end%5D%20%3D%20range%3B%0A%09%09%09if%20(typeof%20start%20!%3D%3D%20%27number%27%20%7C%7C%20(typeof%20end%20!%3D%3D%20%27number%27%20%26%26%20end%20!%3D%3D%20Infinity))%20%7B%0A%09%09%09%09throw%20new%20TypeError(%60Range%20at%20index%20%24%7Bindex%7D%20must%20contain%20valid%20numbers%20or%20Infinity%60)%3B%0A%09%09%09%7D%0A%0A%09%09%09if%20(start%20%3E%3D%20end%20%26%26%20end%20!%3D%3D%20Infinity)%20%7B%0A%09%09%09%09throw%20new%20RangeError(%60In%20range%20at%20index%20%24%7Bindex%7D%2C%20start%20time%20must%20be%20less%20than%20end%20time%60)%3B%0A%09%09%09%7D%0A%0A%09%09%09if%20(index%20%3E%200)%20%7B%0A%09%09%09%09const%20prevEnd%20%3D%20ranges%5Bindex%20-%201%5D%5B1%5D%3B%0A%09%09%09%09if%20(prevEnd%20!%3D%3D%20Infinity%20%26%26%20start%20%3C%3D%20prevEnd)%20%7B%0A%09%09%09%09%09throw%20new%20RangeError(%60Range%20at%20index%20%24%7Bindex%7D%20overlaps%20with%20the%20previous%20range%60)%3B%0A%09%09%09%09%7D%0A%09%09%09%7D%0A%09%09%7D)%3B%0A%09%7D%0A%0A%09validateRanges(option)%3B%0A%0A%09return%20art%20%3D%3E%20%7B%0A%09%09let%20skipRanges%20%3D%20option%3B%0A%0A%09%09function%20updateRanges()%20%7B%0A%09%09%09const%20duration%20%3D%20art.duration%3B%0A%09%09%09skipRanges%20%3D%20skipRanges.map((%5Bstart%2C%20end%5D)%20%3D%3E%20%5B%0A%09%09%09%09start%2C%0A%09%09%09%09end%20%3D%3D%3D%20Infinity%20%3F%20duration%20%3A%20end%0A%09%09%09%5D)%3B%0A%09%09%7D%0A%0A%09%09function%20checkAndSkip()%20%7B%0A%09%09%09const%20currentTime%20%3D%20art.currentTime%3B%0A%09%09%09for%20(const%20%5Bstart%2C%20end%5D%20of%20skipRanges)%20%7B%0A%09%09%09%09if%20(currentTime%20%3E%3D%20start%20%26%26%20currentTime%20%3C%20end)%20%7B%0A%09%09%09%09%09art.seek%20%3D%20end%3B%0A%09%09%09%09%09break%3B%0A%09%09%09%09%7D%0A%09%09%09%7D%0A%09%09%7D%0A%0A%09%09art.on(%27video%3Atimeupdate%27%2C%20checkAndSkip)%3B%0A%09%09art.on(%27video%3Aloadedmetadata%27%2C%20updateRanges)%3B%0A%0A%09%09return%20%7B%0A%09%09%09name%3A%20%27artplayerPluginSkip%27%2C%0A%09%09%09update(newOption%20%3D%20%5B%5D)%20%7B%0A%09%09%09%09validateRanges(newOption)%3B%0A%09%09%09%09skipRanges%20%3D%20newOption%3B%0A%09%09%09%09updateRanges()%3B%0A%09%09%09%7D%0A%09%09%7D%0A%09%7D%0A%7D%0A%0Avar%20art%20%3D%20new%20Artplayer(%7B%0A%09container%3A%20%27.artplayer-app%27%2C%0A%09url%3A%20%27%2Fassets%2Fsample%2Fvideo.mp4%27%2C%0A%09plugins%3A%20%5B%0A%09%09artplayerPluginSkip(%0A%09%09%09%5B%0A%09%09%09%09%5B0%2C%205%5D%2C%0A%09%09%09%09%5B10%2C%2015%5D%2C%0A%09%09%09%09%5B20%2C%20Infinity%5D%20%2F%2F%20Infinity%20is%20duration%20of%20video%0A%09%09%09%5D%0A%09%09)%0A%09%5D%0A%7D)%3B)

itzzzme commented 1 month ago

T

Yes, I wrote an artplayerPluginSkip plugin: demo%20%7B%0A%09%09%09throw%20new%20TypeError(%27Option%20must%20be%20an%20array%20of%20time%20ranges%27)%3B%0A%09%09%7D%0A%0A%09%09ranges.forEach((range%2C%20index)%20%3D%3E%20%7B%0A%09%09%09if%20(!Array.isArray(range)%20%7C%7C%20range.length%20!%3D%3D%202)%20%7B%0A%09%09%09%09throw%20new%20TypeError(%60Range%20at%20index%20%24%7Bindex%7D%20must%20be%20an%20array%20of%20two%20numbers%60)%3B%0A%09%09%09%7D%0A%0A%09%09%09const%20%5Bstart%2C%20end%5D%20%3D%20range%3B%0A%09%09%09if%20(typeof%20start%20!%3D%3D%20%27number%27%20%7C%7C%20(typeof%20end%20!%3D%3D%20%27number%27%20%26%26%20end%20!%3D%3D%20Infinity))%20%7B%0A%09%09%09%09throw%20new%20TypeError(%60Range%20at%20index%20%24%7Bindex%7D%20must%20contain%20valid%20numbers%20or%20Infinity%60)%3B%0A%09%09%09%7D%0A%0A%09%09%09if%20(start%20%3E%3D%20end%20%26%26%20end%20!%3D%3D%20Infinity)%20%7B%0A%09%09%09%09throw%20new%20RangeError(%60In%20range%20at%20index%20%24%7Bindex%7D%2C%20start%20time%20must%20be%20less%20than%20end%20time%60)%3B%0A%09%09%09%7D%0A%0A%09%09%09if%20(index%20%3E%200)%20%7B%0A%09%09%09%09const%20prevEnd%20%3D%20ranges%5Bindex%20-%201%5D%5B1%5D%3B%0A%09%09%09%09if%20(prevEnd%20!%3D%3D%20Infinity%20%26%26%20start%20%3C%3D%20prevEnd)%20%7B%0A%09%09%09%09%09throw%20new%20RangeError(%60Range%20at%20index%20%24%7Bindex%7D%20overlaps%20with%20the%20previous%20range%60)%3B%0A%09%09%09%09%7D%0A%09%09%09%7D%0A%09%09%7D)%3B%0A%09%7D%0A%0A%09validateRanges(option)%3B%0A%0A%09return%20art%20%3D%3E%20%7B%0A%09%09let%20skipRanges%20%3D%20option%3B%0A%0A%09%09function%20updateRanges()%20%7B%0A%09%09%09const%20duration%20%3D%20art.duration%3B%0A%09%09%09skipRanges%20%3D%20skipRanges.map((%5Bstart%2C%20end%5D)%20%3D%3E%20%5B%0A%09%09%09%09start%2C%0A%09%09%09%09end%20%3D%3D%3D%20Infinity%20%3F%20duration%20%3A%20end%0A%09%09%09%5D)%3B%0A%09%09%7D%0A%0A%09%09function%20checkAndSkip()%20%7B%0A%09%09%09const%20currentTime%20%3D%20art.currentTime%3B%0A%09%09%09for%20(const%20%5Bstart%2C%20end%5D%20of%20skipRanges)%20%7B%0A%09%09%09%09if%20(currentTime%20%3E%3D%20start%20%26%26%20currentTime%20%3C%20end)%20%7B%0A%09%09%09%09%09art.seek%20%3D%20end%3B%0A%09%09%09%09%09break%3B%0A%09%09%09%09%7D%0A%09%09%09%7D%0A%09%09%7D%0A%0A%09%09art.on(%27video%3Atimeupdate%27%2C%20checkAndSkip)%3B%0A%09%09art.on(%27video%3Aloadedmetadata%27%2C%20updateRanges)%3B%0A%0A%09%09return%20%7B%0A%09%09%09name%3A%20%27artplayerPluginSkip%27%2C%0A%09%09%09update(newOption%20%3D%20%5B%5D)%20%7B%0A%09%09%09%09validateRanges(newOption)%3B%0A%09%09%09%09skipRanges%20%3D%20newOption%3B%0A%09%09%09%09updateRanges()%3B%0A%09%09%09%7D%0A%09%09%7D%0A%09%7D%0A%7D%0A%0Avar%20art%20%3D%20new%20Artplayer(%7B%0A%09container%3A%20%27.artplayer-app%27%2C%0A%09url%3A%20%27%2Fassets%2Fsample%2Fvideo.mp4%27%2C%0A%09plugins%3A%20%5B%0A%09%09artplayerPluginSkip(%0A%09%09%09%5B%0A%09%09%09%09%5B0%2C%205%5D%2C%0A%09%09%09%09%5B10%2C%2015%5D%2C%0A%09%09%09%09%5B20%2C%20Infinity%5D%20%2F%2F%20Infinity%20is%20duration%20of%20video%0A%09%09%09%5D%0A%09%09)%0A%09%5D%0A%7D)%3B)

Thank you for you help it worked. I am integrating the player in my anime streaming website and I had several questions which I didn't find in the docs

1) how to escape special html tags like "i" tags from subtitle vtt. ( I tried escape=true and encoding="utf-8") but it's still coming) 2) how to change the color of highlight ( I saw the code of Artplayer class but the hightlight only accepts array. Setting color is not mentioned) 3) how to assign custom keys to shortcut like pressing m to mute i to toggle pip mode etc.

Thank you for your helpt btw

zhw2590582 commented 1 month ago
  1. Do you want to display the i or b tags of vtt subtitles? Then you should set escape to false
  2. option.hightlight is relatively simple at present, and not many people use it, so it does not support custom colors, or you can try: artplayer-plugin-chapter
  3. Like this: shortcut%20%7B%0A%09%09art.muted%20%3D%20!art.muted%3B%0A%09%7D%0A%0A%09if%20(event.key.toLowerCase()%20%3D%3D%3D%20%27i%27)%20%7B%0A%09%09art.pip%20%3D%20!art.pip%3B%0A%09%7D%0A%7D)%3B)
itzzzme commented 1 month ago
  1. Do you want to display the i or b tags of vtt subtitles? Then you should set escape to false
  2. option.hightlight is relatively simple at present, and not many people use it, so it does not support custom colors, or you can try: artplayer-plugin-chapter
  3. Like this: shortcut%20%7B%0A%09%09art.muted%20%3D%20!art.muted%3B%0A%09%7D%0A%0A%09if%20(event.key.toLowerCase()%20%3D%3D%3D%20%27i%27)%20%7B%0A%09%09art.pip%20%3D%20!art.pip%3B%0A%09%7D%0A%7D)%3B)

the problem with escape false is when I do that the subtitle get automatically very small

escape:false imageedit_2_6861819417

v/s

escape:true Screenshot 2024-10-20 133112

also is there option to give a color in time slider to particular chapter

zhw2590582 commented 1 month ago

You can customize the font size like this: demo

itzzzme commented 1 month ago

same result here is the code as well as screenshot

import Hls from "hls.js"; import { useEffect, useRef } from "react"; import Artplayer from "artplayer"; import artplayerPluginHlsQuality from "artplayer-plugin-hls-quality"; import artplayerPluginChapter from 'artplayer-plugin-chapter' import artplayerPluginAmbilight from 'artplayer-plugin-ambilight'

function artplayerPluginSkip(option = []) { function validateRanges(ranges) { if (!Array.isArray(ranges)) { throw new TypeError('Option must be an array of time ranges'); }

    ranges.forEach((range, index) => {
        if (!Array.isArray(range) || range.length !== 2) {
            throw new TypeError(`Range at index ${index} must be an array of two numbers`);
        }

        const [start, end] = range;
        if (typeof start !== 'number' || (typeof end !== 'number' && end !== Infinity)) {
            throw new TypeError(`Range at index ${index} must contain valid numbers or Infinity`);
        }

        if (start > end && end !== Infinity) {
            throw new RangeError(`In range at index ${index}, start time must be less than end time`);
        }

        if (index > 0) {
            const prevEnd = ranges[index - 1][1];
            if (prevEnd !== Infinity && start <= prevEnd) {
                throw new RangeError(`Range at index ${index} overlaps with the previous range`);
            }
        }
    });
}

validateRanges(option);

return art => {
    let skipRanges = option;

    function updateRanges() {
        const duration = art.duration;
        skipRanges = skipRanges.map(([start, end]) => [
            start,
            end === Infinity ? duration : end
        ]);
    }

    function checkAndSkip() {
        const currentTime = art.currentTime;
        for (const [start, end] of skipRanges) {
            if (currentTime >= start && currentTime < end) {
                art.seek = end;
                break;
            }
        }
    }

    art.on('video:timeupdate', checkAndSkip);
    art.on('video:loadedmetadata', updateRanges);

    return {
        name: 'artplayerPluginSkip',
        update(newOption = []) {
            validateRanges(newOption);
            skipRanges = newOption;
            updateRanges();
        }
    }
}

}

export default function Player({ streamUrl, subtitles, intro, outro }) { const artRef = useRef(null); 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()); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); }); } else if (video.canPlayType("application/vnd.apple.mpegurl")) { video.src = url; } else { art.notice.show("Unsupported playback format: m3u8"); } }

useEffect(() => {
    if (!streamUrl || !artRef.current) return;

    const art = new Artplayer({
        url: streamUrl,
        container: artRef.current,
        type: "m3u8",
        volume: 1,
        pip: true,
        autoSize: true,
        autoMini: true,
        setting: true,
        loop: true,
        flip: true,
        playbackRate: true,
        aspectRatio: true,
        fullscreen: true,
        subtitleOffset: true,
        miniProgressBar: true,
        mutex: true,
        backdrop: true,
        playsInline: true,
        autoPlayback: true,
        airplay: true,
        plugins: [
            artplayerPluginHlsQuality({
                control: true,
                setting: true,
                getResolution: (level) => level.height + "P",
                title: "Quality",
                auto: "Auto",
            }),
            artplayerPluginSkip(
                [
                    [intro.start, intro.end],
                    [outro.start, outro.end],
                ]
            ),
            artplayerPluginChapter({
                chapters: [
                    { start: intro.start, end: intro.end, title: 'intro' },
                    { start: outro.start, end: outro.end, title: 'outro' },
                ]
            }),
            artplayerPluginAmbilight({
                blur: '50px',
                opacity: 1,
                frequency: 10,
                duration: 0.3,
            }),
        ],
        subtitle: {
            style: {
                fontWeight: "400",
                fontSize: '20px',
                backgroundColor: "rgba(0, 0, 0, 0.65)",
                color: "white",
            },
            escape: false,
        },
        settings: [
            {
                html: "Subtitle",
                tooltip: "Select",
                selector: subtitles?.map((sub) => ({
                    default: sub.label === "English",
                    html: sub.label,
                    url: sub.file,
                })),
                onSelect: function (item) {
                    art.subtitle.switch(item.url, {
                        name: item.html,
                    });
                    return item.html;
                },
            },
        ],
        customType: {
            m3u8: playM3u8,
        }
    });
    document.addEventListener('keydown', function (event) {
        if (event.key.toLowerCase() === 'm') {
            art.muted = !art.muted;
        }

        if (event.key.toLowerCase() === 'i') {
            art.pip = !art.pip;
        }
        if (event.key.toLowerCase() === 'f') {
            art.fullscreen = !art.fullscreen;
        }
    });
    art.on('ready', () => {
        art.focus();
    });
    const defaultSubtitle = subtitles?.find((sub) => sub.label === "English");
    if (defaultSubtitle) {
        art.subtitle.switch(defaultSubtitle.file, {
            name: defaultSubtitle.label,
        });
    }

    art.on("resize", () => {
        art.subtitle.style({
            fontSize: art.height * 0.05 + "px",
            marginBottom: art.height * 0.05 + "px",
        });
    });

    return () => {
        if (art && art.destroy) {
            art.destroy(false);
        }
    };
}, [streamUrl, subtitles, intro, outro]);

return <div ref={artRef} className="w-full h-full"></div>;

}

screenshot Screenshot 2024-10-20 135835

itzzzme commented 1 month ago

Screenshot 2024-10-20 135830

zhw2590582 commented 1 month ago

What is your artplayer version, or is there a demo link?

It may be caused by this code:

   art.on("resize", () => {
        art.subtitle.style({
            fontSize: art.height * 0.05 + "px",
            marginBottom: art.height * 0.05 + "px",
        });
    });
itzzzme commented 1 month ago

my version is 5.2.0, currently the website is in development so I don't have a demo link. Also I don't think the problem is occuring because of this line. because this line should only trigger on resize but if my screen is still and there some text with i or b tag in subtitle then the font size gets automatically small.

also can you kindly tell how to hightlight particular chapter portion with different color