plyr
plyr copied to clipboard
Pass headers to the HTTP reuqest
It would be a great feature to add basic authentication to the video source.
Passing the credentials directly in the link has been dropped by Chrome and more will follow
so http://userb:[email protected]/elon_musk_riding_horse_backwards.mp4 won't pass the credentials any more.
const options = {
headers: {
'Authorization': 'Bearer iaf87h8fhd8idfidf=='
},
tooltips: {
controls: true,
seek: true
},
controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'fullscreen']
};
this.videoPlayer = new Plyr(this.videoPlayerEl.nativeElement, options);
Passing a header to the HTTP request can open all sorts of posibilities for private video streaming
Does your example not achieve the same thing?
headers: {
'Authorization': 'Bearer iaf87h8fhd8idfidf=='
},```
Headers property doesn't exist in the API. That was just my suggestion of who to do that
bumping this :)
bump :)
bump
bump
bump
Bumps won't speed it up. Someone contributing will though. Use those key strokes for good. I don't think this will be a quick change as it will involve loading the content manually rather than using the browsers in-built logic which opens a whole other can of worms.
Bumps won't speed it up. Someone contributing will though. Use those key strokes for good. I don't think this will be a quick change as it will involve loading the content manually rather than using the browsers in-built logic which opens a whole other can of worms.
Hey, I was able to solve this issue very easily using service workers, basically they act as a proxy and can inject custom headers.. even captures the requests required for playing the video.
Browser sends http requests to load the video (200 or 206) -> proxied by service worker's fetch handler -> add custom header -> Pass it normally without any efforts
Here's the service worker code i used to add an auth header:
File: ServiceWorker.js [register this in your main js file]
self.addEventListener("fetch", event => {
if (new URL(event.request.url).origin == "https://www.my_video_endpoint.com/") { //only add header to the endpoint i want
event.respondWith(customHeaderRequestFetch(event));
}
});
function customHeaderRequestFetch(event) {
const newRequest = new Request(event.request, {
mode: "cors",
credentials: "omit",
//also supports partial content (seeking)
headers: {
range:
event.request.headers.get("range") != undefined
? event.request.headers.get("range")
: "0-",
Authorization:
"Bearer " +"xxxABCDEFGHxxx"
}
});
return fetch(newRequest);
}
That's the whole code, now just register this service worker, and let the browser and plyr do all the hard work (automatically) :)
Reference: https://developers.google.com/web/fundamentals/primers/service-workers
I would be happy to send a PR but i am not familiar with the project structure yet. Hope anyone can implement this!
Thanks @Ashesh3! https://github.com/sampotts/plyr/issues/1312#issuecomment-677267757 For anyone who using Angular:
- Create service worker JS file (e.g.
sw.js), place it in some where (e.g.src/assets/js/service-worker/). File content is:
let domainInclude = ''; // Recieve from Component
let authToken = ''; // Recieve from Component
self.addEventListener('install', event => {
const params = new URL(location);
domainInclude = params.searchParams.get('include');
authToken = params.searchParams.get('token');
const installCompleted = Promise.resolve()
.then(() => {});
event.waitUntil(installCompleted);
});
self.addEventListener('activate', event => {
event.waitUntil(
self.clients.claim(),
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cache) => {
if (cache !== cacheName) {
return caches.delete(cache);
}
})
);
}));
});
// This function is implement from the guide of @Ashesh3
self.addEventListener('fetch', event => {
if (event.request.url.includes(domainInclude)) {
event.respondWith(customHeaderRequestFetch(event));
}
});
function customHeaderRequestFetch(event) {
const newRequest = new Request(event.request, {
mode: "cors",
credentials: "omit",
//also supports partial content (seeking)
headers: {
range:
event.request.headers.get("range") != undefined
? event.request.headers.get("range")
: "0-",
Authorization: authToken
}
});
return fetch(newRequest);
}
- Edit file
angular.json: Path:projects><your-pj-name>>architect>build>options>assets:
"assets": [
..........
{
"glob": "**/*",
"input": "src/assets/js/service-worker/",
"output": "/"
}
..........
]
- Go to your Component, where you want to start Service Worker:
ngOnInit() {
if ('serviceWorker' in navigator) { // Make sure browser support Service Worker
navigator.serviceWorker.register(`/sw.js?include=${encodeURIComponent('<URL_MATCHING>')}&token=${<YOUR_TOKEN>}`, {
scope: '<SCOPE>'
})
.then(registration => registration.unregister());
// SW unregistered still working till page reload, so don't worry.
// But if you don't unregister here, Component may get stuck when you reload page,
// because old SW is fetching video and not done yet.
// In this case, If you stop playing video a while, page can be reload.
} else {
window.alert('Browser not support Service Worker.');
}
}
Note:
<URL_MATCHING>: Send tosw.js, using to check matching the endpoint you need to add Headers. E.g./api/video/: If Request URL include/api/video/thensw.jswill adding Headers to the request. (http://backend.local/api/video/aaaaa)<YOUR_TOKEN>: Token send tosw.jsfor attach to Headers.<SCOPE>: Router link wheresw.jswill working, e.g./view-video/(http://localhost:4200/view-video/aaaaa)
Any solution only for html and javascript?
if you are in streming using hls you can easily add header to request by this way
const hls = new Hls({ xhrSetup: xhr => { xhr.setRequestHeader('id', 1) xhr.setRequestHeader('token', 456) } })
Please, i need to add Auhtorization Bearer {{token}} in the requests with Plyr. I'm using with React
import { useRef } from "react";
import Plyr from "plyr-react";
import "plyr-react/plyr.css";
interface VideoPlayerProps {
sources: {
src: string;
size: number;
type: string;
}[];
poster: string;
token: string;
}
function VideoPlayer({ sources, poster, token }: VideoPlayerProps) {
const playerRef = useRef(null);
return (
<Plyr
ref={playerRef}
options={{
// headers: {
// Authorization: "Bearer iaf87h8fhd8idfidf==",
// },
tooltips: {
controls: true,
},
controls: [
"play-large",
"play",
"progress",
"current-time",
"mute",
"volume",
"captions",
"settings",
"fullscreen",
],
settings: [
"quality"
]
}}
source={{
type: "video",
sources: sources,
poster: poster,
}}
/>
);
}
export default VideoPlayer;
I've been able to do "private video streaming" (i.e. video files behind HTTP auth on a separate domain) by first making a simple request via XMLHttpRequest to the server. It can be for anything, so I just request a small icon file that's hidden behind auth. The user then gets asked for credentials by the browser since I don't send the authorization token with the request. After that, videos behind HTTP auth play with Plyr just fine, no crossOrigin needed even if it's on a separate domain. You just need to ensure CORS headers are set up correctly on the other server, of course.
For subtitle tracks, I download the subs through an auth'd XMLHttpRequest, create a blob url for it, and pass the blob url to Plyr. The XMLHttpRequest therefore takes care of any authentication and cross-domain stuff for subs, as the blob url is not cross-domain1.
While this works for me, I have not tried passing the token directly in the initial "small icon" request prior to loading the video resource through Plyr, so I do not know if the browser would "hold onto" that token when making the video request2.
Here's a small sample of how I do it (I'm using React), abbreviated heavily for brevity:
// Step 1. Load some auth'd resource with username/password prompt
let xhr = new XMLHttpRequest();
xhr.withCredentials = true; // This will ask you for credentials first time
xhr.open('GET', 'https://example.com/protected/icon.ico');
xhr.send(); // discard the icon data
// Step 2. Load subtitle file
let subreq = new XMLHttpRequest();
subreq.withCredentials = true; // You already auth'd in step 1, so no prompt this time.
// You may not even need this line, but in my code
// I have request stuff in a function that includes it.
subreq.open('GET', 'https://example.com/protected/subs.vtt');
subreq.send();
const bloburl = URL.createObjectURL(
new Blob([subreq.response], {
type: 'text/vtt',
})
);
// Step 3. Load Plyr. The auth in "Step 1" gets applied to Plyr.
<Plyr
source={{
type: "video",
sources: [{
src: 'https://example.com/protected/video.mp4',
type: "video/mp4",
}],
tracks: [{
kind: 'captions',
label: 'english',
srcLang: 'en',
src: bloburl,
}],
}}
/>
I hope that helps somebody, and sorry if it was confusing/too long. I'm not good at explaining things concisely. I'm also very new to web development, so it's completely possible I'm doing things in a not-so-smart way here. I just know it seems to work consistently for me.
tl;dr
Videos behind auth are loaded normally if you make a prior request (e.g. XMLHttpRequest) that requires auth2. Subtitle tracks can be downloaded via a manual XMLHttpRequest and passed to Plyr via blob url3.
1,3 My subtitles (and videos) are on a separate domain, so I don't know if just placing the URL for same-domain subtitles would also automatically auth like the videos do. However, it seems off-domain subs do not load directly via Plyr without crossOrigin set, but setting as crossOrigin="anonymous" prevented video loading and threw a 401 Unauthorized for the subtitles. Maybe it would work if I set it to the player site's domain, but I did not try that since using a blob works so well.
2 I make the user manually input username/password, so no auth token is directly set/sent in my code. I don't know if sending the token directly over XMLHttpRequest will yield the same behavior. I've only tested using XMLHttpRequest.