Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi Audio supporting #851

Open
itzzzme opened this issue Oct 29, 2024 · 7 comments
Open

Multi Audio supporting #851

itzzzme opened this issue Oct 29, 2024 · 7 comments

Comments

@itzzzme
Copy link

itzzzme commented Oct 29, 2024

Currently is there any option that the player supports multiple audios along with multiple qualities of the hls stream?

@zhw2590582
Copy link
Owner

Are you looking for:artplayer-plugin-hls-control

@itzzzme
Copy link
Author

itzzzme commented Oct 29, 2024

Thanks @zhw2590582! That's exactly what I am looking for

@itzzzme
Copy link
Author

itzzzme commented Oct 30, 2024

@zhw2590582 I had two questions

  1. How to add plugins once the player has already been initiated because once the player has been initiated in order to add a plugin or do any kind of change on pressing button(not associated with player) it doesn't reflect any change.I have to reload the player which is not a viable option.

  2. how to trigger function on video ending. I have tried video:ended from the official docs but it didn't work

@zhw2590582
Copy link
Owner

You can add the plug-in after the player initialization is completed, and then I tested that the video:ended event was triggered normally. demo

@itzzzme
Copy link
Author

itzzzme commented Oct 30, 2024

in demo what you have done in this part

art.on('ready', () => {
	art.plugins.add(function yourPlugin(art) {
		console.log('yourPlugin');
	});
});

currently I am doing exactly this but what I am saying is on changing of some variable if you try to add some plugins it doesn't work

my implementation

image

but in this code problem is after initialization of player if autoSkipInto variable changes it doesn't affect the plugins adding

scenario: initaily the autoSkipIntro was false and player has initialized but in between of playing video if it gets true the it doesn't do that plugins adding portion

@itzzzme
Copy link
Author

itzzzme commented Oct 30, 2024

image

getting this error when adding this lines

art.on("video:ended", () => {
      console.log("---->", "video:ended");
    });

my full code

import Hls from "hls.js";
import { useEffect, useRef } from "react";
import Artplayer from "artplayer";
import artplayerPluginHlsQuality from "artplayer-plugin-hls-quality";
import artplayerPluginChapter from "./artPlayerPluinChaper";
import artplayerPluginSkip from "./autoSkip";
import artplayerPluginVttThumbnail from "./artPlayerPluginVttThumbnail";
import {
  backward10Icon,
  captionIcon,
  forward10Icon,
  fullScreenOffIcon,
  fullScreenOnIcon,
  loadingIcon,
  logo,
  muteIcon,
  pauseIcon,
  pipIcon,
  playIcon,
  playIconLg,
  settingsIcon,
  volumeIcon,
} from "./PlayerStyle";
import "./Player.css";
import website_name from "@/src/config/website";
import getChapterStyles from "./getChapterStyle";

const KEY_CODES = {
  M: "m",
  I: "i",
  F: "f",
  V: "v",
  SPACE: " ",
  ARROW_UP: "arrowup",
  ARROW_DOWN: "arrowdown",
  ARROW_RIGHT: "arrowright",
  ARROW_LEFT: "arrowleft",
};

export default function Player({
  streamUrl,
  subtitles,
  thumbnail,
  intro,
  outro,
  autoSkipIntro,
  autoPlay,
}) {
  const artRef = useRef(null);
  const proxy = import.meta.env.VITE_PROXY_URL;
  const m3u8proxy = import.meta.env.VITE_M3U8_PROXY_URL;

  useEffect(() => {
    const applyChapterStyles = () => {
      const existingStyles = document.querySelectorAll(
        "style[data-chapter-styles]"
      );
      existingStyles.forEach((style) => style.remove());
      const styleElement = document.createElement("style");
      styleElement.setAttribute("data-chapter-styles", "true");
      const styles = getChapterStyles(intro, outro);
      styleElement.textContent = styles;
      document.head.appendChild(styleElement);
      return () => {
        styleElement.remove();
      };
    };

    if (streamUrl || intro || outro) {
      const cleanup = applyChapterStyles();
      return cleanup;
    }
  }, [streamUrl, intro, outro]);

  const 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");
    }
  };

  const createChapters = () => {
    const chapters = [];
    if (intro?.start !== 0 || intro?.end !== 0) {
      chapters.push({ start: intro.start, end: intro.end, title: "intro" });
    }
    if (outro?.start !== 0 || outro?.end !== 0) {
      chapters.push({ start: outro.start, end: outro.end, title: "outro" });
    }
    return chapters;
  };

  const handleKeydown = (event, art) => {
    const tagName = event.target.tagName.toLowerCase();

    if (tagName === "input" || tagName === "textarea") return;

    switch (event.key.toLowerCase()) {
      case KEY_CODES.M:
        art.muted = !art.muted;
        break;
      case KEY_CODES.I:
        art.pip = !art.pip;
        break;
      case KEY_CODES.F:
        event.preventDefault();
        event.stopPropagation();
        art.fullscreen = !art.fullscreen;
        break;
      case KEY_CODES.V:
        event.preventDefault();
        event.stopPropagation();
        art.subtitle.show = !art.subtitle.show;
        break;
      case KEY_CODES.SPACE:
        event.preventDefault();
        event.stopPropagation();
        art.playing ? art.pause() : art.play();
        break;
      case KEY_CODES.ARROW_UP:
        event.preventDefault();
        event.stopPropagation();
        art.volume = Math.min(art.volume + 0.1, 1);
        break;
      case KEY_CODES.ARROW_DOWN:
        event.preventDefault();
        event.stopPropagation();
        art.volume = Math.max(art.volume - 0.1, 0);
        break;
      case KEY_CODES.ARROW_RIGHT:
        event.preventDefault();
        event.stopPropagation();
        art.currentTime = Math.min(art.currentTime + 10, art.duration);
        break;
      case KEY_CODES.ARROW_LEFT:
        event.preventDefault();
        event.stopPropagation();
        art.currentTime = Math.max(art.currentTime - 10, 0);
        break;
      default:
        break;
    }
  };

  useEffect(() => {
    if (!streamUrl || !artRef.current) return;
    Artplayer.CONTEXTMENU = false;
    const art = new Artplayer({
      url: m3u8proxy + streamUrl,
      container: artRef.current,
      type: "m3u8",
      volume: 1,
      setting: true,
      playbackRate: true,
      pip: true,
      hotkey: false,
      fullscreen: true,
      mutex: true,
      playsInline: true,
      autoPlayback: true,
      lock: true,
      airplay: true,
      autoOrientation: true,
      fastForward: true,
      autoplay: autoPlay,
      plugins: [
        artplayerPluginHlsQuality({
          setting: true,
          getResolution: (level) => level.height + "P",
          title: "Quality",
          auto: "Auto",
        }),
        artplayerPluginChapter({ chapters: createChapters() }),
      ],
      subtitle: {
        style: {
          "font-weight": "400",
          "background-color": "rgba(0, 0, 0, 0.65)",
          height: "fit-content",
          width: "fit-content",
          marginInline: "auto",
          "margin-top": "auto",
          "margin-bottom": "2rem",
          left: "50%",
          transform: "translateX(-50%)",
          color: "#fff",
        },
        escape: false,
      },
      layers: [
        {
          name: website_name,
          html: logo,
          tooltip: website_name,
          style: {
            opacity: 1,
            position: "absolute",
            top: "5px",
            right: "5px",
            transition: "opacity 0.5s ease-out",
          },
        },
      ],
      controls: [
        {
          html: backward10Icon,
          position: "right",
          tooltip: "Backward 10s",
          click: () => {
            art.currentTime = Math.max(art.currentTime - 10, 0);
          },
        },
        {
          html: forward10Icon,
          position: "right",
          tooltip: "Forward 10s",
          click: () => {
            art.currentTime = Math.min(art.currentTime + 10, art.duration);
          },
        },
      ],
      icons: {
        play: playIcon,
        pause: pauseIcon,
        setting: settingsIcon,
        volume: volumeIcon,
        pip: pipIcon,
        volumeClose: muteIcon,
        state: playIconLg,
        loading: loadingIcon,
        fullscreenOn: fullScreenOnIcon,
        fullscreenOff: fullScreenOffIcon,
      },
      customType: {
        m3u8: playM3u8,
      },
    });
    art.on("resize", () => {
      art.subtitle.style({
        fontSize:
          (art.height > 500 ? art.height * 0.02 : art.height * 0.04) + "px",
      });
    });

    art.on("ready", () => {
      document.addEventListener("keydown", (event) =>
        handleKeydown(event, art)
      );
      art.subtitle.style({
        fontSize:
          (art.height > 500 ? art.height * 0.02 : art.height * 0.04) + "px",
      });
      thumbnail &&
        art.plugins.add(
          artplayerPluginVttThumbnail({
            vtt: `${proxy}${thumbnail}`,
          })
        );
      subtitles &&
        subtitles.length > 0 &&
        art.setting.add({
          name: "captions",
          icon: captionIcon,
          html: "Subtitle",
          tooltip:
            subtitles.find((sub) => sub.label.toLowerCase() === "english")
              ?.label || "default",
          position: "right",
          selector: [
            {
              html: "Display",
              switch: true,
              onSwitch: function (item) {
                item.tooltip = item.switch ? "Hide" : "Show";
                art.subtitle.show = !item.switch;
                return !item.switch;
              },
            },
            ...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;
          },
        });
      console.log(autoSkipIntro);
      autoSkipIntro &&
        art.plugins.add(
          artplayerPluginSkip([
            ...(intro.start && intro.end ? [[intro.start, intro.end]] : []),
            ...(outro.start && outro.end ? [[outro.start, outro.end]] : []),
          ])
        );
      setTimeout(() => {
        art.layers[website_name].style.opacity = 0;
      }, 2000);
    });

    const defaultSubtitle = subtitles?.find((sub) => sub.label === "English");
    if (defaultSubtitle) {
      art.subtitle.switch(defaultSubtitle.file, {
        name: defaultSubtitle.label,
      });
    }
    art.on("video:ended", () => {
      console.log("---->", "video:ended");
    });
    return () => {
      if (art && art.destroy) {
        art.destroy(false);
      }
    };
  }, [streamUrl, subtitles, intro, outro, autoSkipIntro]);

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

@itzzzme
Copy link
Author

itzzzme commented Oct 30, 2024

I have tested the m3u8 link with several other option and also with standalone hls.js it's not giving any error. But this error is only coming in artplayer when tyring some events (i.e: video:ended in this case)

here is the comparison

with artplayer it can't read some packets

2024-10-30.19-29-59.online-video-cutter.com.mp4

same link with normal hls.js standalone library

2024-10-30.19-29-59.online-video-cutter.com.1.mp4

it will be really helpful if you can tell the issue here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants