Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ build
coverage
dist
node_modules
examples
*.sublime-*
*.vscode*
*.md
28 changes: 28 additions & 0 deletions examples/nextjs-with-typescript/certificates/localhost-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: These are locally created keys for testing https servers locally (a pre-requisite for some DRM test cases). No concerns with these being pushed to remote repo.

MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDGCe0MWB02oNTY
UIwcXh8HR6ryYE6eACVriOK12OeIwhKnR+DLyVwCMvcxhP8cnQNAFtv5m3GLkIqC
IQlN/LEoJgO0keer63PvMpAAngXEPB5DbBDA4HouNDPKRncWONYry3DxafCFV0GF
FXIPpnkEoAa7WthnawTY0L+RxCNKYPLLr1ysISfwQtT/iI44qY0yceBrHlzJcXHm
X4WzvM2SHBBrSxwQiDq3s1kbZG/jh3m7bPiaDi+dAtw13nAPrcvr+5y8InKQlHKg
fZMDUsqDLsYWTPRWKfNcuHmrzh09nemk59qEydWs4FfmCingga1/pPm8J/9v7QOR
bV5zegRnAgMBAAECggEAfer1xDGKcZ+VdezRCYB27iQNd4rwsdmZ8WPZ8pH1tajg
P2iecDSZwfQx5FSR+NmEpY1jVLQXYAwJZXAoAGQ5KDPziy5yRYex8VcO5LWVKQ6o
l2nwxRJ13Ubn3ycqtQByfOof5//aJI40ZLAcFANfUyeE4D+E9UzAijNAeyhg3/rg
YSoKEG9gvJzedXclawgASOq1yvnFeOT7IVwRg1mKbQ0noyDtKZIUo4pCK2/l5PF5
5ZYQwD97xgGfS/I6yYZlnUgq0TH8gioHCyDI+hNLMlCtUeHlENU3Ysdc3xACfdP4
dp7Cy2d+M0RamYrGEpHu7TT/ZhNA5uomk8KbTZmkoQKBgQDIc3XPzDBsbJt0tNoq
mOtXfWwZ6wgaC7/L8eO+/SUVupCe64mNkIXKtAPNIzMp2+WhKWscHhIv4iMQrDA4
HSlA5pRdTdvt7Nrv5N92AzhXygr9DjLXyVPOduN8HpRBOWR0URgSIgFh+syKlo8q
7+vJAdYd8jXWAJ6XkHj3C3gl7wKBgQD861WuFWq5Gye4QJ7df3GqdwpthQqSsELu
B0/n/XUrHXkCM5XcunnN/OKv5gtMafTwGxG1nlmHD/QGFo3Zm++nffomlx/2mcf6
NGmPogtmqOWqE8RnXH9lfZvh7Ya3rCkaPXdhmp0LWX8w+7+PTdHCCz6OXJyv9tzO
lUBxFlFBCQKBgGqHGexGGtH0YiWC25LZ0/CaIjIf+x1Ecziio6NjiyriDGu4x1Bp
pwDT7FU/yLgNOhsNFPRLcuTprDL3H2Ui8kKgh+aSMzhdsPjezHc+PNpC8NYNjq2p
PBW0jy7uXWHQa3d5hW5VjiCRFdTtMMbj3I3loPInP78scxwfVnoMKV0XAoGBAIG5
972N8Kq3Wf0w8Atuhg/IdUnNlqm8zOeoSn0UzRdrS5ksem64Gyfj0SYl2Z+9LUxG
piPA7+zN6v2Abguy5w7DGB7ZHyTupdsZLRfmJvDmKr682t2lXRbigaU1nwbwwDhc
VLJ0ip66rfmi9xN6998Ow2xj5l5/QayYc3BN4Sl5AoGAUe/Y9OeUmEh3Dq+CRyhs
Lxd9/+iU6Zyhm9mxOaIbXpp+OwujdG0qc7WKGLM4Wh15SW0BUjSnmNCKE9dlLQeR
UVy0Sog82mnCNrkHSz/bml0DbE0lUkizonOQz8oCgDkkfE/DRzMQYn52NyJ4YjRS
TCsc0dPlXDm0VB7jGwy6QWs=
-----END PRIVATE KEY-----
26 changes: 26 additions & 0 deletions examples/nextjs-with-typescript/certificates/localhost.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEaDCCAtCgAwIBAgIRAKC0znDOShrNYYEqvpF/at8wDQYJKoZIhvcNAQELBQAw
gYsxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEwMC4GA1UECwwnY3Bp
bGxzYnVyeUBDaHJpc3RpYW5zLU1hY0Jvb2stUHJvLmxvY2FsMTcwNQYDVQQDDC5t
a2NlcnQgY3BpbGxzYnVyeUBDaHJpc3RpYW5zLU1hY0Jvb2stUHJvLmxvY2FsMB4X
DTI1MDcxNDE4MzY0NVoXDTI3MTAxNDE4MzY0NVowWzEnMCUGA1UEChMebWtjZXJ0
IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTAwLgYDVQQLDCdjcGlsbHNidXJ5QENo
cmlzdGlhbnMtTWFjQm9vay1Qcm8ubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDGCe0MWB02oNTYUIwcXh8HR6ryYE6eACVriOK12OeIwhKnR+DL
yVwCMvcxhP8cnQNAFtv5m3GLkIqCIQlN/LEoJgO0keer63PvMpAAngXEPB5DbBDA
4HouNDPKRncWONYry3DxafCFV0GFFXIPpnkEoAa7WthnawTY0L+RxCNKYPLLr1ys
ISfwQtT/iI44qY0yceBrHlzJcXHmX4WzvM2SHBBrSxwQiDq3s1kbZG/jh3m7bPia
Di+dAtw13nAPrcvr+5y8InKQlHKgfZMDUsqDLsYWTPRWKfNcuHmrzh09nemk59qE
ydWs4FfmCingga1/pPm8J/9v7QORbV5zegRnAgMBAAGjdjB0MA4GA1UdDwEB/wQE
AwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBRZEmSxNIWPLl2V
EgeR3bfAE1KlqDAsBgNVHREEJTAjgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAA
AAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggGBAEuhrXSh1NpIteqcUJ7fmRFfFsxc
/4EhWWtrXv/TEmitVROQR1+MQ/ImZUL9EzruZl7qxbmoqufoA41645ez666kRy5U
tD6/Ide/X01LyT+V5S26RxfQIHZKEHaXrwM1vMoceso6MwUIEsX1jsyd3/XoMwOy
+1Oe1CVzfwa8kckT8QUWniEiJRX+DLOp3VFEkcsaXzV9snGI6e97CoskJuZdeyBK
W2SVXqHXK5lz4h7dRTGV0MqprwZLE4dUyBQVVr0MpRaHJ1YktSbdMAxvJiSRfUFY
d0WgmhnCtN1drY5ZT2oTY2SvvP6xJUgdhTEKNyLtFSQBNTtUH1iZqTTl/F0xiaLw
8NWNXUjsFzJky0rNNajOVLGEOct45r+p4KTyYvaHeHIiLmKn8igOmUV0Nt3rH8t+
6vpXguUc0IZFwuphe0I5dKQy1dzyWDgw4CMFEYxy+SZ0OJ9xP6T4T3rQkKveNnI1
3gCuGmf2IhhAvx0AJ61PEMD3EhR4ULKKRiTzsg==
-----END CERTIFICATE-----
153 changes: 153 additions & 0 deletions examples/nextjs-with-typescript/components/SimpleVideoPlayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/* eslint-disable no-console */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a SimpleVideoPlayer that use hls.js and adds Mux Video DRM + playback token support.

import type { DRMSystemsConfiguration } from 'hls.js';
import HLS from 'hls.js';
import { useEffect, useRef } from 'react';

// import type { Props } from '../types';
type Props = { playbackId: string; playbackData: PlaybackData };

/**
* Represents the data needed to play a video.
*
* For videos protected by DRM, both tokens are needed. The playback token
* provides access and the DRM token allows for decryption.
*
* Playback policies control who can view content, while DRM provides additional
* encryption to prevent unauthorized copying.
*
* @see https://www.mux.com/docs/guides/secure-video-playback
* @see https://www.mux.com/docs/guides/protect-videos-with-drm
*/
export interface PlaybackData {
/**
* Unique identifier for the video asset.
*/
playbackId: string;
/**
* URL of the video including the playback token.
*/
tokens: {
/**
* JSON Web Token (JWT) used to acquire the DRM license.
*/
drm?: string;
/**
* JSON Web Token (JWT) used to authenticate the signed playback policy.
*/
playback?: string;
};
}

/**
* The types of licenses that Mux supports.
*
* @see https://www.mux.com/docs/guides/protect-videos-with-drm#generic-player-integration
*/
type LicenseType = 'widevine' | 'fairplay' | 'playready';

/**
* Constructs the Mux-specific license URL for the given license type for the
* given playback data.
*
* @see https://www.mux.com/docs/guides/protect-videos-with-drm#generic-player-integration
*/
const licenseUrlFor = (type: LicenseType, { playbackId, tokens: { drm: drmToken } }: PlaybackData) => {
return `https://license.mux.com/license/${type}/${playbackId}?token=${drmToken}`;
};

/**
* Constructs the Mux-specific server certificate URL for the given license
* type for the given playback data.
*
* @see https://www.mux.com/docs/guides/protect-videos-with-drm#generic-player-integration
*/
const serverCertificateUrlFor = (type: LicenseType, { playbackId, tokens: { drm: drmToken } }: PlaybackData) => {
return `https://license.mux.com/appcert/${type}/${playbackId}?token=${drmToken}`;
};

/**
* Returns the HLS DRM systems configuration for the given playback data. The
* fields we need to provide in this configuration are determined by what Mux
* supports.
*
* @see https://github.com/video-dev/hls.js/blob/master/docs/API.md#drmsystems
* @see https://www.mux.com/docs/guides/protect-videos-with-drm#widevine
* @see https://www.mux.com/docs/guides/protect-videos-with-drm#fairplay
* @see https://www.mux.com/docs/guides/protect-videos-with-drm#playready
*/
export const drmSystemsConfig = (playbackData: PlaybackData): DRMSystemsConfiguration => ({
'com.widevine.alpha': {
licenseUrl: licenseUrlFor('widevine', playbackData),
},
'com.apple.fps': {
licenseUrl: licenseUrlFor('fairplay', playbackData),
serverCertificateUrl: serverCertificateUrlFor('fairplay', playbackData),
},
'com.microsoft.playready': {
licenseUrl: licenseUrlFor('playready', playbackData),
},
});

export const SimpleVideoPlayer = (props: PlaybackData) => {
const { playbackId, tokens: { playback: playbackToken, drm: drmToken } = {} } = props;
const videoRef = useRef<HTMLVideoElement>(null);
const videoUrl = `https://stream.mux.com/${playbackId}.m3u8?token=${playbackToken}`;

useEffect(() => {
if (!videoRef.current) {
return;
}

if (!HLS.isSupported()) {
videoRef.current.src = videoUrl;
return;
}

const hls = new HLS({
debug: true,

/**
* When there is playback data, we're using Mux's DRM system, so we
* need to enable Encrypted Media Extensions (EME).
*
* @see https://en.wikipedia.org/wiki/Encrypted_Media_Extensions
* @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
* @see https://github.com/video-dev/hls.js/blob/master/docs/API.md#emeenabled
*/
emeEnabled: !!drmToken,

/**
* Describes the DRM systems that are supported by our DRM provider (Mux).
*
* @see https://github.com/video-dev/hls.js/blob/master/docs/API.md#drmsystems
*/
drmSystems: drmToken ? drmSystemsConfig(props) : {},
});

hls.loadSource(videoUrl);
hls.attachMedia(videoRef.current);

hls.on(HLS.Events.ERROR, (event, data) => {
if (data.fatal) {
switch (data.type) {
case HLS.ErrorTypes.NETWORK_ERROR:
console.log('Network error occurred', event, data);
break;
case HLS.ErrorTypes.MEDIA_ERROR:
console.log('Media error occurred', event, data);
break;
case HLS.ErrorTypes.OTHER_ERROR:
console.log('An unknown error occurred', event, data);
break;
}
}
});
}, [props, videoUrl]);

return (
<video ref={videoRef} controls>
<track kind="captions" />
</video>
);
};
/* eslint-enable no-console */
1 change: 1 addition & 0 deletions examples/nextjs-with-typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@mux/mux-uploader-react": ">=1.0.0-beta.0",
"@mux/mux-video": ">=0.3.0",
"@mux/mux-video-react": ">=0.3.0",
"hls.js": "^1.6.7",
"media-chrome": "^4.9.0",
"next": "^14.2.2",
"react": "^18.2.0",
Expand Down
64 changes: 64 additions & 0 deletions examples/nextjs-with-typescript/pages/SimpleVideoPlayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Head from 'next/head';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a test page for using the <SimpleVideoPlayer/> component. Works similar to our <MuxPlayer/> test page though not designed for similar robustness. Expect to have to apply parameters and then load the resultant URL for expected behavior.

import type { NextParsedUrlQuery } from 'next/dist/server/request-meta';
import { PlaybackData, SimpleVideoPlayer } from '../components/SimpleVideoPlayer';
import { TextRenderer } from '../components/renderers';
import URLPathRenderer from '../components/URLPathRenderer';
import ComponentCodeRenderer from '../components/ComponentCodeRenderer';
import { LocationProps, getLocationServerSideProps, usePageStateReducer } from '../app/page-state';

const DEFAULT_INITIAL_STATE: Partial<PlaybackData> = Object.freeze({
tokens: undefined,
playbackId: undefined,
});

const toInitialState = (query: NextParsedUrlQuery) => {
const queryState = Object.fromEntries(Object.entries(query).map(([k, v]) => [k, JSON.parse(v as string)]));
const initialState = {
...DEFAULT_INITIAL_STATE,
...queryState,
};
return initialState as Partial<PlaybackData>;
};

export const getServerSideProps = getLocationServerSideProps;

type Props = LocationProps;

function MuxPlayerPage({ location }: Props) {
/** @TODO fix typing complexities here (CJP) */
// @ts-ignore
const [state, _dispatch, genericOnChange] = usePageStateReducer<PlaybackData>(toInitialState, [undefined]);

return (
<>
<Head>
<title>&lt;MuxPlayer/&gt; Demo</title>
</Head>
<main className="component-page">
<SimpleVideoPlayer
// ref={mediaElRef}
// Test _hlsConfig for MuxPlayer (react) (Note: This also indirectly tests <mux-player> & <mux-video>)
// _hlsConfig={{
// startLevel: 2,
// debug: true,
// }}
playbackId={state.playbackId}
tokens={state.tokens}
/>

<div className="options">
<ComponentCodeRenderer state={state} component="SimpleVideoPlayerPlayer" />
<URLPathRenderer state={state} location={typeof window !== 'undefined' ? window.location : location} />
<div>
<h2>Manual Config</h2>
</div>
<TextRenderer value={state.playbackId} name="playbackId" onChange={genericOnChange} />
<TextRenderer value={state.tokens?.playback} name="tokens.playback" onChange={genericOnChange} />
<TextRenderer value={state.tokens?.drm} name="tokens.drm" onChange={genericOnChange} />
</div>
</main>
</>
);
}

export default MuxPlayerPage;
Loading
Loading