plyr
plyr copied to clipboard
Quality switcher for HLS
It appears that HLS quality switching is a highly requested feature and several other players seem to have this plugin. I see the ground work was laid with this commit/issue below. Is there any time frame to implement this feature? I think overall it's a great player and loads HLS videos faster for me than other open source players.
#1607
Upvoting, this is really a much need feature
Hi
We have implemented Plyr to our project, but have also found, that Plyr by default do not support multiple qualities for HLS. We needed it, so we tried to implement it.
The diff of our solution can be found at https://gist.github.com/Matho/b88d10da98471c114ca1a855882bbc85 It is not cleanest solution and a lot of work I have did in Ruby, because it was simple easier for me. Take this as the one of a way, or demonstration.
In ruby code, I'm downloading and parsing m3u8 file. Then, I'm extracting src links with quality information and passing to the html5 video element as various src elements. This should be done in javascript.
If you check the code in plyr.js.coffee, you can see how I switch the qualities. On quality change, I'm loading new file for HLS library. Because I know the seek time at that time, I set the seek time to be equal when playing new file. Thanks to it, I can continue with playing from the same point with different quality.
We have found bug, which is I think specific to our project. When I have activated the pip mode and changed the http url in browser, the pip video paused. So I have implemented the fix. It is the fix with browser_paused_pip variable.
I'm not saving quality to local storage (see storage option). Instead, I'm selecting the 'middle' quality as the default quality, when the player starts.
Maybe this will help you all. Maybe we will rewrite the m3u8 parsing to JS one day. Or maybe somebody will help me with this step?
Thanks and have a nice day
Hello folks, here's our solution in TS - it basically uses the levels parsed from the manifest and adds each quality automatically, hope it helps anyone:
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
this.player = this.loadPlayer();
});
loadPlayer() {
const playerOptions = {
quality: {
default: '720',
options: ['720']
}
};
// If HLS is supported (ie non-mobile), we add video quality settings
if (Hls.isSupported()) {
playerOptions.quality = {
default: this.hls.levels[this.hls.levels.length - 1].height,
options: this.hls.levels.map((level) => level.height),
forced: true,
// Manage quality changes
onChange: (quality: number) => {
this.hls.levels.forEach((level, levelIndex) => {
if (level.height === quality) {
this.hls.currentLevel = levelIndex;
}
});
}
};
}
this.player = new Plyr(this.videoElement.nativeElement, playerOptions);
// Start HLS load on play event
this.player.on('play', () => this.hls.startLoad());
// Handle HLS quality changes
this.player.on('qualitychange', () => {
if (this.player.currentTime !== 0) {
this.hls.startLoad();
}
});
return this.player;
}
Hi @ThomasCantonnet Many thanks for sharing your code! I did the parsing in Ruby, now I can rewrite the app to do it with hls.js
Is there another issue keeping track of this or is this the canonical place for this feature?
Add the following code in the <head>
<script src="https://cdn.plyr.io/3.5.10/plyr.polyfilled.js"></script>
<script src="https://cdn.dashjs.org/latest/dash.all.min.js"></script>
<link rel="stylesheet" href="https://cdn.plyr.io/3.5.10/plyr.css" />
Add the following code in the
// Here is the data format
var data = [
{
src: 'https://bitmovin-a.akamaihd.net/content/sintel/sintel.mpd',
size: 720,
mode: 'mpd',// How to analyze
},
{
// Resource address
src: 'https://vod02.cdn.web.tv/am/8c/am8cvvsejym_,240,360,480,720,1080,.mp4.urlset/master.m3u8',
size: 1080,// Definition
mode: 'hls'// How to analyze
},
];
player.source = {
// type: 'audio',
type: 'video',
title: '新播放',
sources: data,
};
player.on('qualitychange', event => {
$.each(data, function () {
initData();
})
});
function initData () {
const video = document.querySelector('video');
$.each(data, function () {
// hls Adaptation
if (this.mode === 'hls' && this.size === player.config.quality.selected) {
// For more options see: https://github.com/sampotts/plyr/#options
// captions.update is required for captions to work with hls.js
if (!Hls.isSupported()) {
video.src = this.src;
} else {
const hls = new Hls();
hls.loadSource(this.src);
hls.attachMedia(video);
window.hls = hls;
// Handle changing captions
player.on('languagechange', () => {
// Caption support is still flaky. See: https://github.com/sampotts/plyr/issues/994
setTimeout(() => hls.subtitleTrack = player.currentTrack, 50);
});
}
// Expose player so it can be used from the console
window.player = player;
return false;
}
// dash Adaptation
if (this.mode === 'mpd' && this.size === player.config.quality.selected) {
// For more dash options, see https://github.com/Dash-Industry-Forum/dash.js
const dash = dashjs.MediaPlayer().create();
dash.initialize(video, this.src, true);
// Expose player and dash so they can be used from the console
window.player = player;
window.dash = dash;
}
})
}
initData();
Then you can run your code to see how it works. If you have any questions, please consult [email protected]
but a single .m3u8 can have support for multiple qualities within it
Please implement this feature.
Anyone figured out how to workaround the file not found error message, when initialising the player after hls?
GET blob:http://1.localhost:4300/b12d9d80-92a3-47b6-95e8-d29d1e8c98f1 net::ERR_FILE_NOT_FOUND
I tried using what @ThomasCantonnet has suggested using plain JS but it seems the hls levels in not propagated to the playerOptions can @IT-Pony and @sampotts shed some light on this.
My Implementation looking at Thomas's snippet in TS
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>HLS Demo</title>
<link rel="stylesheet" href="https://cdn.plyr.io/3.5.10/plyr.css" />
<style>
body {
max-width: 1024px;
}
</style>
</head>
<body>
<video preload="none" id="player" autoplay controls crossorigin></video>
<script src="https://cdn.plyr.io/3.5.10/plyr.js"></script>
<script src="https://cdn.jsdelivr.net/hls.js/latest/hls.js"></script>
<script>
(function () {
var video = document.querySelector('#player');
var playerOptions= {
quality: {
default: '720',
options: ['720']
}
};
var player;
player = new Plyr(video,playerOptions);
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource('https://content.jwplatform.com/manifests/vM7nH0Kl.m3u8');
//hls.loadSource('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8');
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED,function(event,data) {
console.log('levels', hls.levels);
playerOptions.quality = {
default: hls.levels[hls.levels.length - 1].height,
options: hls.levels.map((level) => level.height),
forced: true,
// Manage quality changes
onChange: (quality) => {
console.log('changes',quality);
hls.levels.forEach((level, levelIndex) => {
if (level.height === quality) {
hls.currentLevel = levelIndex;
}
});
}
};
});
}
// Start HLS load on play event
player.on('play', () => hls.startLoad());
// Handle HLS quality changes
player.on('qualitychange', () => {
console.log('changed');
if (player.currentTime !== 0) {
hls.startLoad();
}
});
})();
</script>
</body>
</html>
After hours of trying and testing, these tricks worked for me. I now have swich quality button, and it maintain the current playing time, too :D
- Make Plyr treat HLS link like video/mp4 so you will be able to access the switch quality button menu.
- Make sure when user click play button, the HLS will load, or the play won't work (because Plyr couldn't recognize the .m3u8 file).
- Check event on qualitychange, then reset the HLS link each time user click on it.
HTML
<link rel="stylesheet" href="//path/to/plyr.css" />
<div class="container">
<video id="player" width="100%" dura="" src="" poster="" data-plyr-config='{"quality":{"default": 480}}' preload="none" controlsList="nodownload" controls crossorigin playsinline poster="">
<source src="//path/to/video_480.m3u8" type="video/mp4" size="480"/>
<source src="//path/to/video_720.m3u8" type="video/mp4" size="720"/>
<source src="//path/to/video_1080.m3u8" type="video/mp4" size="1080"/>
</video>
<script src="//path/to/plyr.js"></script>
<script src="//path/to/hls.min.js"></script>
JS
<script>
document.addEventListener('DOMContentLoaded', () => {
var video = document.querySelector('video');
var default_src = jQuery('source[size=480]')[0];
var player = new Plyr('#susu_player');
var first = true;
player.on('play', () => {
if(first) {
source = default_src.getAttribute('src');
loadHLS(source);
}
first = false;
});
var hls = []; var i = 0;
function loadHLS(source) {
hls[i] = new Hls();
hls[i].loadSource(source);
hls[i].attachMedia(video);
video.play();
i++;
}
// quality change
player.on('qualitychange', () => {
seek = player.currentTime;
source = video.getAttribute('src');
loadHLS(source);
});
</script>
Tested on Chrome & Safari.
@phuongncn Thanks for spending time on this issue but , in most cases with hls format , the quality is parsed via the manifest file and not passed in as source which makes it dynamic. This way we need to get hold of the streams which may or may not be possible
```
hls.on(Hls.Events.MANIFEST_PARSED,function(event,data) {
console.log('levels', hls.levels); // this is the quality level defined
playerOptions.quality = {
default: hls.levels[hls.levels.length - 1].height,
options: hls.levels.map((level) => level.height),
forced: true,
}
It's nice to see the participation on this issue. Maybe someone could put together a full good working code here so it's not spread out over several replies. @phuongncn @rahulrsingh09 @rahulrsingh09 Thanks
Using VOD HLS with Quality Switch works flawless, but I'm trying to make them jump to a specific time of the video, but it's not working. @phuongncn I'm using your example, how would you make it jump to a specific time ? Thanks in Advance
All of this does not reliably work with mpeg dash
Why isn't https://github.com/sampotts/plyr/pull/1607 getting merged?
Why isn't #1607 getting merged?
Read through the comments on it and you'll see what happened. I ended up having to manually merge the changes. https://github.com/sampotts/plyr/commit/6ffaef35cf667d0e2e14227882a1a8e329b2c2c2
Hey everyone, here is my working example of combining with Hls.js
and plyr
. The main idea is to configure option properly based on recent PR by @sampotts .
TLDR: working example https://codepen.io/datlife/pen/dyGoEXo
Main Idea
The goal is to set qualitiy
options based on MANIFEST data loaded by HLS.
Notes before jumping into code
- By streaming video using HLS protocol, we may just need to add single
source
tag in HTML (not multiplemp4
sources in the Plyr example). The reason is all available qualities are in the MANIFEST ofplaylist.m3u8
. Therefore, we'd like theHLS
performs the switching, notPlyr
.
For example, our HTML video will be something like:
<video controls crossorigin playsinline >
<source
type="application/x-mpegURL"
<!-- playlist contains all available qualities for this stream --->
src="https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8">
</video>
- The manifest's playlist contains all available qualities, subtitles. Notice it has different
RESOLUTIONS
for the same video.
#EXTM3U
...
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=258157,CODECS="avc1.4d400d,mp4a.40.2",AUDIO="stereo",RESOLUTION=422x180,SUBTITLES="subs"
video/250kbit.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=520929,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272,SUBTITLES="subs"
video/500kbit.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=831270,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272,SUBTITLES="subs"
video/800kbit.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1144430,CODECS="avc1.4d401f,mp4a.40.2",AUDIO="surround",RESOLUTION=958x408,SUBTITLES="subs"
video/1100kbit.m3u8
....
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Deutsch",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="de",URI="subtitles_de.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="en",URI="subtitles_en.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Espanol",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="es",URI="subtitles_es.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="fr",URI="subtitles_fr.m3u8"
Implementation
document.addEventListener("DOMContentLoaded", () => {
const video = document.querySelector("video");
const source = video.getElementsByTagName("source")[0].src;
// For more options see: https://github.com/sampotts/plyr/#options
const defaultOptions = {};
if (Hls.isSupported()) {
// For more Hls.js options, see https://github.com/dailymotion/hls.js
const hls = new Hls();
hls.loadSource(source);
// From the m3u8 playlist, hls parses the manifest and returns
// all available video qualities. This is important, in this approach,
// we will have one source on the Plyr player.
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
// Transform available levels into an array of integers (height values).
const availableQualities = hls.levels.map((l) => l.height)
// Add new qualities to option
defaultOptions.quality = {
default: availableQualities[0],
options: availableQualities,
// this ensures Plyr to use Hls to update quality level
// Ref: https://github.com/sampotts/plyr/blob/master/src/js/html5.js#L77
forced: true,
onChange: (e) => updateQuality(e),
}
// Initialize new Plyr player with quality options
const player = new Plyr(video, defaultOptions);
});
hls.attachMedia(video);
window.hls = hls;
} else {
// default options with no quality update in case Hls is not supported
const player = new Plyr(video, defaultOptions);
}
function updateQuality(newQuality) {
window.hls.levels.forEach((level, levelIndex) => {
if (level.height === newQuality) {
console.log("Found quality match with " + newQuality);
window.hls.currentLevel = levelIndex;
}
});
}
});
@datlife nice solution, the only problem I had with a similar implementation was the error in the console, when plyr tries to load the blob that hls sets as the video src.
GET blob:http://localhost:4200/d16a5b78-1f6b-4f90-af7f-149e92a820be net::ERR_FILE_NOT_FOUND
You found a way around that?
Anyone figured out how to set an 'auto' option in the quality options? My new idea is adding all qualities that could be available and set them all to display: none and then dynamically set display: flex for all the available qualities after the manifest has been parsed. Only problem atm is, that I can not set a string value as default or choose a string value from the list.
I'm also still not able to get @datlife solution to work. It doesn't even give me quality button. @sampotts is there no hope of getting an actual universal feature implemented into the player itself instead of all the different custom solutions?
Well although there are nice solutions from different people there's no one standard correctly and a place to put actual plugins. There seems to be a PR for plugin support but seems PR's are behind. For now I'm going with VideoJs because it has everything I need including several HLS switcher plugins that work and the original reason for me switching to Plyr is fixed. Plyr is a good player but may try again when it's more mature.
You can check how to display multi-resolution mpeg dash source on the following link https://codepen.io/adis0308/pen/bGpQmwr
Anyone figured out how to set an 'auto' option in the quality options? My new idea is adding all qualities that could be available and set them all to display: none and then dynamically set display: flex for all the available qualities after the manifest has been parsed. Only problem atm is, that I can not set a string value as default or choose a string value from the list.
- add custom 'Auto' label to getLabel function
getLabel: function (e, t) {
...
case "quality":
//inside if add below
if (t === 0) return "Auto";
- add listener Hls.Events.LEVEL_SWITCHED
if (Hls.isSupported()) {
var hls = new Hls(config)
hls.loadSource(source);
hls.attachMedia(video);
window.hls = hls;
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
const availableQualities = hls.levels.map((l) => l.height)
availableQualities.unshift(0) //prepend 0 to quality array
defaultOptions.quality = {
default: 0, //Default - AUTO
options: availableQualities,
forced: true,
onChange: (e) => updateQuality(e),
}
hls.on(Hls.Events.LEVEL_SWITCHED, function (event, data) {
var span = document.querySelector(".plyr__menu__container [data-plyr='quality'][value='0'] span")
if (hls.autoLevelEnabled) {
span.innerHTML = `AUTO (${hls.levels[data.level].height}p)`
} else {
span.innerHTML = `AUTO`
}
})
var player = new Plyr(video, defaultOptions);
})
}
function updateQuality(newQuality) {
if (newQuality === 0) {
window.hls.currentLevel = -1; //Enable AUTO quality if option.value = 0
} else {
window.hls.levels.forEach((level, levelIndex) => {
if (level.height === newQuality) {
console.log("Found quality match with " + newQuality);
window.hls.currentLevel = levelIndex;
}
});
}
}
@Dirard, Sorry if my question seems so obvious! Where is the getLabel
function? Can you please provide the complete code?
@Dirard, Sorry if my question seems so obvious! Where is the
getLabel
function? Can you please provide the complete code?
https://github.com/sampotts/plyr/blob/30989e4dbc6acb9d1caf4a83af4d6cd12d2548db/dist/plyr.mjs#L2412
Hi @datlife, thanks a lot for the amazing solution. If it won't take a lot of your time, could you please share a way of adding such a support for multiple videos on a page? The code you provided works for one video flawlessly, except for the not-so-important 404 error as @Benny739 mentioned. However, as soon as I modify it to support multiple videos on a single page, things stop working. Plyr just doesn't load the video at all. I can only see the video thumbnail and no trace of Plyr actually loading the video (that is, no class names change, no controls rendered, nothing).
Here's how I have set it up:
var video = document.querySelectorAll('video');
for (var i = 0; i < video.length; i++)
{
var source = video[i].getElementsByTagName('source')[0].src;
var plyrOptions =
{
previewThumbnails:
{
enabled: true,
src: video[i].getAttribute('thumbnails')
}
};
if (Hls.isSupported())
{
var hls = new Hls();
hls.loadSource(source);
hls.on(Hls.Events.MANIFEST_PARSED, function()
{
var availableQualities = hls.levels.map((l) => l.height)
plyrOptions.quality =
{
forced: true,
options: availableQualities,
default: availableQualities[0],
onChange: (e) => updateQuality(e),
}
var player = Plyr.setup('video[i]', plyrOptions); // changing it to Plyr.setup(video[i], plyrOptions); makes no difference
});
hls.attachMedia(video[i]);
window.hls = hls;
}
else
{
var player = Plyr.setup('video[i]', plyrOptions);
}
Adding Plyr before the hls.on...
loads the video, however, only the first one. I am completely lost even after hours of trying various stuff.
Any help would be greatly appreciated. I was using Plyr on my previous website, but, moved to Video JS 2 days back for this new website, for the sole reason that it has support for HLS quality (using plugins). But the styling and any kind of modification and the overall look and feel brought me back to Plyr.
EDIT: Done! Fixed it! Now I use it like this: var player = Plyr.setup('.vid', plyrOptions);. Finally!
Okay, I was wrong. It's still not working as expected. The video resolution would always be stuck to the same one even if we choose another one from menu. Kindly help.
@Dirard, Sorry if my question seems so obvious! Where is the
getLabel
function? Can you please provide the complete code?https://github.com/sampotts/plyr/blob/30989e4dbc6acb9d1caf4a83af4d6cd12d2548db/dist/plyr.mjs#L2412
Hi, This changes the text in the list of available qualities but when we go back of the main menu, it shows the text "0dp" when auto is selected. Everything else is working perfectly fine.
@Dirard, Sorry if my question seems so obvious! Where is the
getLabel
function? Can you please provide the complete code?
You dont need to change getLabel function
There is a better approach
using i18n and passing qualityLabel
in conifg object to it
like this :
new Plyr(playerEl, {
...,
i18n: {
qualityLabel: {
0: 'Auto',
},
},
})
Just for reference. This is a working example with many suggestion in this thread (getLabel stuff, quality switch). The @datlife plus.
document.addEventListener('DOMContentLoaded', () => {
const source = video.getElementsByTagName("source")[0].src;
const video = document.querySelector('video');
const defaultOptions = {};
if (!Hls.isSupported()) {
video.src = source;
var player = new Plyr(video, defaultOptions);
} else {
// For more Hls.js options, see https://github.com/dailymotion/hls.js
const hls = new Hls();
hls.loadSource(source);
// From the m3u8 playlist, hls parses the manifest and returns
// all available video qualities. This is important, in this approach,
// we will have one source on the Plyr player.
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
// Transform available levels into an array of integers (height values).
const availableQualities = hls.levels.map((l) => l.height)
availableQualities.unshift(0) //prepend 0 to quality array
// Add new qualities to option
defaultOptions.quality = {
default: 0, //Default - AUTO
options: availableQualities,
forced: true,
onChange: (e) => updateQuality(e),
}
// Add Auto Label
defaultOptions.i18n = {
qualityLabel: {
0: 'Auto',
},
}
hls.on(Hls.Events.LEVEL_SWITCHED, function (event, data) {
var span = document.querySelector(".plyr__menu__container [data-plyr='quality'][value='0'] span")
if (hls.autoLevelEnabled) {
span.innerHTML = `AUTO (${hls.levels[data.level].height}p)`
} else {
span.innerHTML = `AUTO`
}
})
// Initialize new Plyr player with quality options
var player = new Plyr(video, defaultOptions);
});
hls.attachMedia(video);
window.hls = hls;
}
function updateQuality(newQuality) {
if (newQuality === 0) {
window.hls.currentLevel = -1; //Enable AUTO quality if option.value = 0
} else {
window.hls.levels.forEach((level, levelIndex) => {
if (level.height === newQuality) {
console.log("Found quality match with " + newQuality);
window.hls.currentLevel = levelIndex;
}
});
}
}
});
For anyone who using react, this is how I do it.
p/s: I took the code from plyr-react
then I implemented quality selector.
https://www.npmjs.com/package/plyr-react
import Hls from "hls.js";
import PlyrJS, { Options, PlyrEvent as PlyrJSEvent, SourceInfo } from "plyr";
import React, { HTMLProps, MutableRefObject, useEffect, useRef } from "react";
import PropTypes from "prop-types";
import "plyr/dist/plyr.css";
export type PlyrInstance = PlyrJS;
export type PlyrEvent = PlyrJSEvent;
export type PlyrCallback = (this: PlyrJS, event: PlyrEvent) => void;
export type PlyrProps = HTMLProps<HTMLVideoElement> & {
source?: SourceInfo;
options?: Options;
};
export interface HTMLPlyrVideoElement {
plyr?: PlyrInstance;
}
export const Plyr = React.forwardRef<HTMLPlyrVideoElement, PlyrProps>(
(props, ref) => {
const { options = null, source, ...rest } = props;
const innerRef = useRef<HTMLPlyrVideoElement>();
const hls = useRef(new Hls());
const videoOptions: PlyrJS.Options = {
...options,
quality: {
default: 720,
options: [720],
},
};
const createPlayer = () => {
const plyrPlayer = new PlyrJS(".plyr-react", videoOptions);
if (innerRef.current?.plyr) {
innerRef.current.plyr = plyrPlayer;
}
};
hls.current.on(Hls.Events.MANIFEST_LOADED, () => {
videoOptions.quality = {
default: hls.current.levels[hls.current.levels.length - 1].height,
options: hls.current.levels.map((level) => level.height),
forced: true,
// Manage quality changes
onChange: (quality: number) => {
hls.current.levels.forEach((level, levelIndex) => {
if (level.height === quality) {
hls.current.currentLevel = levelIndex;
}
});
},
};
createPlayer();
});
useEffect(() => {
if (!innerRef.current) return;
if (Hls.isSupported()) {
hls.current.loadSource(source?.sources[0].src!);
hls.current.attachMedia(innerRef.current as HTMLMediaElement);
} else {
createPlayer();
}
if (typeof ref === "function") {
if (innerRef.current) ref(innerRef.current);
} else {
if (ref && innerRef.current) ref.current = innerRef.current;
}
if (innerRef.current?.plyr && source) {
innerRef.current.plyr.source = source;
}
innerRef.current.plyr?.on("play", () => hls.current.startLoad());
innerRef.current.plyr?.on("qualitychange", () => {
if (innerRef.current?.plyr?.currentTime !== 0) {
hls.current.startLoad();
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [videoOptions]);
return (
<video
ref={innerRef as unknown as MutableRefObject<HTMLVideoElement>}
className="plyr-react plyr"
{...rest}
/>
);
}
);
Hey everyone, here is my working example of combining with
Hls.js
andplyr
. The main idea is to configure option properly based on recent PR by @sampotts .TLDR: working example https://codepen.io/datlife/pen/dyGoEXo
Main Idea
The goal is to set
qualitiy
options based on MANIFEST data loaded by HLS.Notes before jumping into code
- By streaming video using HLS protocol, we may just need to add single
source
tag in HTML (not multiplemp4
sources in the Plyr example). The reason is all available qualities are in the MANIFEST ofplaylist.m3u8
. Therefore, we'd like theHLS
performs the switching, notPlyr
.For example, our HTML video will be something like:
<video controls crossorigin playsinline > <source type="application/x-mpegURL" <!-- playlist contains all available qualities for this stream ---> src="https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8"> </video>
- The manifest's playlist contains all available qualities, subtitles. Notice it has different
RESOLUTIONS
for the same video.#EXTM3U ... #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=258157,CODECS="avc1.4d400d,mp4a.40.2",AUDIO="stereo",RESOLUTION=422x180,SUBTITLES="subs" video/250kbit.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=520929,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272,SUBTITLES="subs" video/500kbit.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=831270,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272,SUBTITLES="subs" video/800kbit.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1144430,CODECS="avc1.4d401f,mp4a.40.2",AUDIO="surround",RESOLUTION=958x408,SUBTITLES="subs" video/1100kbit.m3u8 .... #EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Deutsch",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="de",URI="subtitles_de.m3u8" #EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="en",URI="subtitles_en.m3u8" #EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Espanol",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="es",URI="subtitles_es.m3u8" #EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="fr",URI="subtitles_fr.m3u8"
Implementation
document.addEventListener("DOMContentLoaded", () => { const video = document.querySelector("video"); const source = video.getElementsByTagName("source")[0].src; // For more options see: https://github.com/sampotts/plyr/#options const defaultOptions = {}; if (Hls.isSupported()) { // For more Hls.js options, see https://github.com/dailymotion/hls.js const hls = new Hls(); hls.loadSource(source); // From the m3u8 playlist, hls parses the manifest and returns // all available video qualities. This is important, in this approach, // we will have one source on the Plyr player. hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) { // Transform available levels into an array of integers (height values). const availableQualities = hls.levels.map((l) => l.height) // Add new qualities to option defaultOptions.quality = { default: availableQualities[0], options: availableQualities, // this ensures Plyr to use Hls to update quality level // Ref: https://github.com/sampotts/plyr/blob/master/src/js/html5.js#L77 forced: true, onChange: (e) => updateQuality(e), } // Initialize new Plyr player with quality options const player = new Plyr(video, defaultOptions); }); hls.attachMedia(video); window.hls = hls; } else { // default options with no quality update in case Hls is not supported const player = new Plyr(video, defaultOptions); } function updateQuality(newQuality) { window.hls.levels.forEach((level, levelIndex) => { if (level.height === newQuality) { console.log("Found quality match with " + newQuality); window.hls.currentLevel = levelIndex; } }); } });
Hi, Could you implement the AUTO option to change the quality automatically? Thank you.