Responsive Design
This commit is contained in:
parent
73723741b5
commit
8172c2122e
89
cdrm-frontend/package-lock.json
generated
89
cdrm-frontend/package-lock.json
generated
@ -11,7 +11,9 @@
|
|||||||
"@tailwindcss/vite": "^4.1.4",
|
"@tailwindcss/vite": "^4.1.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-helmet": "^6.1.0",
|
||||||
"react-router-dom": "^7.5.2",
|
"react-router-dom": "^7.5.2",
|
||||||
|
"shaka-player": "^4.14.9",
|
||||||
"tailwindcss": "^4.1.4"
|
"tailwindcss": "^4.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -1872,6 +1874,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/eme-encryption-scheme-polyfill": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-GzgrLuZPYGijd8oaKuBqYv3Tc2oruDZM3V2982KOuy/PA1N3zwMe+/oIXJYfZ3BH3PwW5nONdBBE+VY6jlwbrw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.18.1",
|
"version": "5.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||||
@ -2358,7 +2366,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
@ -2696,6 +2703,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"loose-envify": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
@ -2758,6 +2777,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/object-assign": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||||
@ -2897,6 +2925,17 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prop-types": {
|
||||||
|
"version": "15.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^16.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@ -2928,6 +2967,42 @@
|
|||||||
"react": "^19.1.0"
|
"react": "^19.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-fast-compare": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/react-helmet": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react-fast-compare": "^3.1.1",
|
||||||
|
"react-side-effect": "^2.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-helmet/node_modules/react-side-effect": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.17.0",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||||
@ -3048,6 +3123,18 @@
|
|||||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/shaka-player": {
|
||||||
|
"version": "4.14.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/shaka-player/-/shaka-player-4.14.9.tgz",
|
||||||
|
"integrity": "sha512-0YZJ+UUHBz3meAzN/eOvjNoLH7eCG1yHP3BFH8ZnFbGf3K50DgLWNMoV6bm6pH4cndvXem+SdcQRXabItD4RBA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"eme-encryption-scheme-polyfill": "^2.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
@ -13,7 +13,9 @@
|
|||||||
"@tailwindcss/vite": "^4.1.4",
|
"@tailwindcss/vite": "^4.1.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-helmet": "^6.1.0",
|
||||||
"react-router-dom": "^7.5.2",
|
"react-router-dom": "^7.5.2",
|
||||||
|
"shaka-player": "^4.14.9",
|
||||||
"tailwindcss": "^4.1.4"
|
"tailwindcss": "^4.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Home from "./components/Pages/HomePage";
|
import Home from "./components/Pages/HomePage";
|
||||||
|
import Cache from "./components/Pages/Cache";
|
||||||
|
import API from "./components/Pages/API";
|
||||||
|
import TestPlayer from "./components/Pages/TestPlayer";
|
||||||
import NavBar from "./components/NavBar";
|
import NavBar from "./components/NavBar";
|
||||||
import NavBarMain from "./components/NavBarMain";
|
import NavBarMain from "./components/NavBarMain";
|
||||||
import SideMenu from "./components/SideMenu"; // Add this import
|
import SideMenu from "./components/SideMenu"; // Add this import
|
||||||
@ -13,18 +16,21 @@ function App() {
|
|||||||
{/* The SideMenu should be visible when isMenuOpen is true */}
|
{/* The SideMenu should be visible when isMenuOpen is true */}
|
||||||
<SideMenu isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} />
|
<SideMenu isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} />
|
||||||
|
|
||||||
<div id="navbarcontainer" className="hidden lg:flex lg:w-2xs bg-gray-950/55 border-r border-white/5 shadow-lg shadow-blue-900/50">
|
<div id="navbarcontainer" className="hidden lg:flex lg:w-2xs bg-gray-950/55 border-r border-white/5 0">
|
||||||
<NavBar />
|
<NavBar />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="maincontainer" className="w-full lg:w-5/6 bg-gray-950/50 flex flex-col">
|
<div id="maincontainer" className="w-full lg:w-5/6 bg-gray-950/50 flex flex-col grow">
|
||||||
<div id="navbarmaincontainer" className="w-full lg:hidden h-16 bg-gray-950/10 border-b border-white/5 shadow-md shadow-blue-900/35 sticky top-0 z-10">
|
<div id="navbarmaincontainer" className="w-full lg:hidden h-16 bg-gray-950/10 border-b border-white/5 sticky top-0 z-10">
|
||||||
<NavBarMain setIsMenuOpen={setIsMenuOpen} />
|
<NavBarMain setIsMenuOpen={setIsMenuOpen} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="maincontentcontainer" className="w-full grow">
|
<div id="maincontentcontainer" className="w-full grow overflow-y-auto">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
|
<Route path="/cache" element={<Cache />} />
|
||||||
|
<Route path="/api" element={<API />} />
|
||||||
|
<Route path="/testplayer" element={<TestPlayer />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
173
cdrm-frontend/src/components/Functions/ParseChallenge.jsx
Normal file
173
cdrm-frontend/src/components/Functions/ParseChallenge.jsx
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import "./protobuf.min.js";
|
||||||
|
import "./license_protocol.min.js";
|
||||||
|
|
||||||
|
const { SignedMessage, LicenseRequest } = protobuf.roots.default.license_protocol;
|
||||||
|
|
||||||
|
function uint8ArrayToBase64(uint8Array) {
|
||||||
|
const binaryString = Array.from(uint8Array)
|
||||||
|
.map(b => String.fromCharCode(b))
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
return btoa(binaryString);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFetch(fetchString) {
|
||||||
|
// Remove `await` if it exists in the string
|
||||||
|
fetchString = fetchString.replace(/^await\s+/, "");
|
||||||
|
|
||||||
|
// Use a more lenient regex to capture the fetch pattern (including complex bodies)
|
||||||
|
const fetchRegex = /fetch\(['"](.+?)['"],\s*(\{.+?\})\)/s; // Non-greedy match for JSON
|
||||||
|
const lines = fetchString.split('\n').map(line => line.trim()).filter(Boolean);
|
||||||
|
const result = {
|
||||||
|
method: 'UNDEFINED',
|
||||||
|
url: '',
|
||||||
|
headers: {},
|
||||||
|
body: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try matching the regex
|
||||||
|
const fetchMatch = fetchString.match(fetchRegex);
|
||||||
|
if (!fetchMatch) {
|
||||||
|
console.log(fetchString);
|
||||||
|
throw new Error("Invalid 'Copy as fetch' string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract URL from the match
|
||||||
|
result.url = fetchMatch[1];
|
||||||
|
|
||||||
|
// Parse the options JSON from the match (this will include headers, body, etc.)
|
||||||
|
const optionsString = fetchMatch[2];
|
||||||
|
const options = JSON.parse(optionsString);
|
||||||
|
|
||||||
|
// Assign method, headers, and body if available
|
||||||
|
if (options.method) result.method = options.method;
|
||||||
|
if (options.headers) result.headers = options.headers;
|
||||||
|
if (options.body) result.body = options.body;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const WIDEVINE_SYSTEM_ID = new Uint8Array([0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed]);
|
||||||
|
const PLAYREADY_SYSTEM_ID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]);
|
||||||
|
const PSSH_MAGIC = new Uint8Array([0x70, 0x73, 0x73, 0x68]);
|
||||||
|
|
||||||
|
function intToUint8Array(num, endian) {
|
||||||
|
const buffer = new ArrayBuffer(4);
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
view.setUint32(0, num, endian);
|
||||||
|
return new Uint8Array(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shortToUint8Array(num, endian) {
|
||||||
|
const buffer = new ArrayBuffer(2);
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
view.setUint16(0, num, endian);
|
||||||
|
return new Uint8Array(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function psshDataToPsshBoxB64(pssh_data, system_id) {
|
||||||
|
const dataLength = pssh_data.length;
|
||||||
|
const totalLength = dataLength + 32;
|
||||||
|
const pssh = new Uint8Array([
|
||||||
|
...intToUint8Array(totalLength, false),
|
||||||
|
...PSSH_MAGIC,
|
||||||
|
...new Uint8Array(4),
|
||||||
|
...system_id,
|
||||||
|
...intToUint8Array(dataLength, false),
|
||||||
|
...pssh_data
|
||||||
|
]);
|
||||||
|
return uint8ArrayToBase64(pssh);
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrmHeaderToPlayReadyHeader(wrm_header){
|
||||||
|
const playready_object = new Uint8Array([
|
||||||
|
...shortToUint8Array(1, true),
|
||||||
|
...shortToUint8Array(wrm_header.length, true),
|
||||||
|
...wrm_header
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new Uint8Array([
|
||||||
|
...intToUint8Array(playready_object.length + 2 + 4, true),
|
||||||
|
...shortToUint8Array(1, true),
|
||||||
|
...playready_object
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeUtf16LE(str) {
|
||||||
|
const buffer = new Uint8Array(str.length * 2);
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
const code = str.charCodeAt(i);
|
||||||
|
buffer[i * 2] = code & 0xff;
|
||||||
|
buffer[i * 2 + 1] = code >> 8;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringToUint8Array(string) {
|
||||||
|
return Uint8Array.from(string.split("").map(x => x.charCodeAt()));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readTextFromClipboard() {
|
||||||
|
try {
|
||||||
|
// Request text from the clipboard
|
||||||
|
const clipboardText = await navigator.clipboard.readText();
|
||||||
|
|
||||||
|
const result = parseFetch(clipboardText);
|
||||||
|
|
||||||
|
let pssh_data_string;
|
||||||
|
let payload_string;
|
||||||
|
|
||||||
|
if (result.body.startsWith("<")) {
|
||||||
|
// If body starts with "<", process it as PlayReady content
|
||||||
|
payload_string = result.body;
|
||||||
|
const wrmHeaderMatch = payload_string.match(/.*(<WRMHEADER.*<\/WRMHEADER>).*/);
|
||||||
|
const wrmHeader = wrmHeaderMatch ? wrmHeaderMatch[1] : null;
|
||||||
|
const encodedWrmHeader = encodeUtf16LE(wrmHeader);
|
||||||
|
const playreadyHeader = wrmHeaderToPlayReadyHeader(encodedWrmHeader);
|
||||||
|
pssh_data_string = psshDataToPsshBoxB64(playreadyHeader, PLAYREADY_SYSTEM_ID);
|
||||||
|
} else {
|
||||||
|
// If body is in a different format, process as Widevine content
|
||||||
|
const uint8Array = stringToUint8Array(result.body);
|
||||||
|
let signed_message;
|
||||||
|
let license_request;
|
||||||
|
try {
|
||||||
|
signed_message = SignedMessage.decode(uint8Array);
|
||||||
|
license_request = LicenseRequest.decode(signed_message.msg);
|
||||||
|
} catch (decodeError) {
|
||||||
|
// If error occurs during decoding, return an empty pssh
|
||||||
|
console.error('Decoding failed, returning empty pssh', decodeError);
|
||||||
|
pssh_data_string = ''; // Empty pssh if decoding fails
|
||||||
|
}
|
||||||
|
|
||||||
|
if (license_request && license_request.contentId && license_request.contentId.widevinePsshData) {
|
||||||
|
const pssh_data = license_request.contentId.widevinePsshData.psshData[0];
|
||||||
|
pssh_data_string = psshDataToPsshBoxB64(pssh_data, WIDEVINE_SYSTEM_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the body contains binary data (non-UTF-8 characters)
|
||||||
|
if (isBinary(uint8Array)) {
|
||||||
|
payload_string = uint8ArrayToBase64(uint8Array);
|
||||||
|
} else {
|
||||||
|
// If it's text, return it as is
|
||||||
|
payload_string = result.body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output the result
|
||||||
|
document.getElementById("licurl").value = result.url;
|
||||||
|
document.getElementById("headers").value = JSON.stringify(result.headers);
|
||||||
|
document.getElementById("pssh").value = pssh_data_string;
|
||||||
|
document.getElementById("data").value = payload_string;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to read clipboard contents:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if the data is binary
|
||||||
|
function isBinary(uint8Array) {
|
||||||
|
// Check for non-text (non-ASCII) bytes (basic heuristic)
|
||||||
|
return uint8Array.some(byte => byte > 127); // Non-ASCII byte indicates binary
|
||||||
|
}
|
||||||
|
|
||||||
|
|
1
cdrm-frontend/src/components/Functions/license_protocol.min.js
vendored
Normal file
1
cdrm-frontend/src/components/Functions/license_protocol.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
8
cdrm-frontend/src/components/Functions/protobuf.min.js
vendored
Normal file
8
cdrm-frontend/src/components/Functions/protobuf.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
134
cdrm-frontend/src/components/Pages/API.jsx
Normal file
134
cdrm-frontend/src/components/Pages/API.jsx
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Helmet } from 'react-helmet'; // Import Helmet
|
||||||
|
|
||||||
|
const { protocol, hostname, port } = window.location;
|
||||||
|
|
||||||
|
let fullHost = `${protocol}//${hostname}`;
|
||||||
|
if (
|
||||||
|
(protocol === 'http:' && port !== '80') ||
|
||||||
|
(protocol === 'https:' && port !== '443' && port !== '')
|
||||||
|
) {
|
||||||
|
fullHost += `:${port}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function API() {
|
||||||
|
const [deviceInfo, setDeviceInfo] = useState({
|
||||||
|
device_type: '',
|
||||||
|
system_id: '',
|
||||||
|
security_level: '',
|
||||||
|
host: '',
|
||||||
|
secret: '',
|
||||||
|
device_name: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const [prDeviceInfo, setPrDeviceInfo] = useState({
|
||||||
|
security_level: '',
|
||||||
|
host: '',
|
||||||
|
secret: '',
|
||||||
|
device_name: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Fetch Widevine info
|
||||||
|
fetch('/remotecdm/widevine/deviceinfo')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
setDeviceInfo({
|
||||||
|
device_type: data.device_type,
|
||||||
|
system_id: data.system_id,
|
||||||
|
security_level: data.security_level,
|
||||||
|
host: data.host,
|
||||||
|
secret: data.secret,
|
||||||
|
device_name: data.device_name
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching Widevine info:', error));
|
||||||
|
|
||||||
|
// Fetch PlayReady info
|
||||||
|
fetch('/remotecdm/playready/deviceinfo')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
setPrDeviceInfo({
|
||||||
|
security_level: data.security_level,
|
||||||
|
host: data.host,
|
||||||
|
secret: data.secret,
|
||||||
|
device_name: data.device_name
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching PlayReady info:', error));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col w-full overflow-y-auto p-4 text-white">
|
||||||
|
<Helmet>
|
||||||
|
<title>API</title>
|
||||||
|
</Helmet>
|
||||||
|
<details open className='w-full list-none'>
|
||||||
|
<summary className='text-2xl'>Sending a decryption request</summary>
|
||||||
|
<div className='mt-5 p-5 rounded-lg border-2 border-indigo-500/50'>
|
||||||
|
<pre className='rounded-lg font-mono whitespace-pre-wrap text-white overflow-auto'>
|
||||||
|
{`import requests
|
||||||
|
|
||||||
|
print(requests.post(
|
||||||
|
url='${fullHost}/api/decrypt',
|
||||||
|
headers={
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
json={
|
||||||
|
'pssh': 'AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA==',
|
||||||
|
'licurl': 'https://cwip-shaka-proxy.appspot.com/no_auth',
|
||||||
|
'headers': str({
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0',
|
||||||
|
'Accept': '*/*',
|
||||||
|
'Accept-Language': 'en-US,en;q=0.5',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
).json()['message'])`}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details open className='w-full list-none mt-5'>
|
||||||
|
<summary className='text-2xl'>Sending a search request</summary>
|
||||||
|
<div className='mt-5 border-2 border-indigo-500/50 p-5 rounded-lg'>
|
||||||
|
<pre className="rounded-lg font-mono whitespace-pre text-white overflow-x-auto max-w-full p-5">
|
||||||
|
{`import requests
|
||||||
|
|
||||||
|
print(requests.post(
|
||||||
|
url='${fullHost}/api/cache/search',
|
||||||
|
json={
|
||||||
|
'input': 'AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA=='
|
||||||
|
}
|
||||||
|
).json())`}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details open className='w-full list-none mt-5'>
|
||||||
|
<summary className='text-2xl'>PyWidevine RemoteCDM info</summary>
|
||||||
|
<div className='mt-5 border-2 border-indigo-500 p-5 rounded-lg overflow-x-auto'>
|
||||||
|
<p>
|
||||||
|
<strong>Device Type:</strong> '{deviceInfo.device_type}'<br />
|
||||||
|
<strong>System ID:</strong> {deviceInfo.system_id}<br />
|
||||||
|
<strong>Security Level:</strong> {deviceInfo.security_level}<br />
|
||||||
|
<strong>Host:</strong> {fullHost}/remotecdm/widevine<br />
|
||||||
|
<strong>Secret:</strong> {deviceInfo.secret}<br />
|
||||||
|
<strong>Device Name:</strong> {deviceInfo.device_name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details open className='w-full list-none mt-5'>
|
||||||
|
<summary className='text-2xl'>PyPlayready RemoteCDM info</summary>
|
||||||
|
<div className='mt-5 border-2 border-indigo-500 p-5 rounded-lg overflow-x-auto'>
|
||||||
|
<p>
|
||||||
|
<strong>Security Level:</strong> {prDeviceInfo.security_level}<br />
|
||||||
|
<strong>Host:</strong> {fullHost}/remotecdm/playready<br />
|
||||||
|
<strong>Secret:</strong> {prDeviceInfo.secret}<br />
|
||||||
|
<strong>Device Name:</strong> {prDeviceInfo.device_name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default API;
|
107
cdrm-frontend/src/components/Pages/Cache.jsx
Normal file
107
cdrm-frontend/src/components/Pages/Cache.jsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
import { Helmet } from 'react-helmet'; // Import Helmet
|
||||||
|
|
||||||
|
function Cache() {
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [cacheData, setCacheData] = useState([]);
|
||||||
|
const [keyCount, setKeyCount] = useState(0); // New state to store the key count
|
||||||
|
const debounceTimeout = useRef(null);
|
||||||
|
|
||||||
|
// Fetch the key count when the component mounts
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchKeyCount = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/cache/keycount');
|
||||||
|
const data = await response.json();
|
||||||
|
setKeyCount(data.count); // Update key count
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching key count:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchKeyCount();
|
||||||
|
}, []); // Run only once when the component mounts
|
||||||
|
|
||||||
|
const handleInputChange = (event) => {
|
||||||
|
const query = event.target.value;
|
||||||
|
setSearchQuery(query); // Update the search query
|
||||||
|
|
||||||
|
// Clear the previous timeout
|
||||||
|
if (debounceTimeout.current) {
|
||||||
|
clearTimeout(debounceTimeout.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a new timeout to send the API call after 1 second of no typing
|
||||||
|
debounceTimeout.current = setTimeout(() => {
|
||||||
|
if (query.trim() !== '') {
|
||||||
|
sendApiCall(query); // Only call the API if the query is not empty
|
||||||
|
} else {
|
||||||
|
setCacheData([]); // Clear results if query is empty
|
||||||
|
}
|
||||||
|
}, 1000); // 1 second delay
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendApiCall = (text) => {
|
||||||
|
fetch('/api/cache/search', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ input: text }),
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => setCacheData(data)) // Update cache data with the results
|
||||||
|
.catch((error) => console.error('Error:', error));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col w-full h-full overflow-y-auto p-4">
|
||||||
|
<Helmet>
|
||||||
|
<title>Cache</title>
|
||||||
|
</Helmet>
|
||||||
|
<div className="flex flex-col lg:flex-row w-full lg:h-12 items-center">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder={`Search ${keyCount} keys...`} // Dynamic placeholder
|
||||||
|
className="lg:grow w-full border-2 border-emerald-500/25 rounded-xl h-10 self-center m-2 text-white p-1 focus:outline-none focus:ring-2 focus:ring-emerald-500/50 transition-all duration-200 ease-in-out"
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
href="/api/cache/download"
|
||||||
|
className="bg-emerald-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-10 truncate w-full text-center flex items-center justify-center m-2"
|
||||||
|
>
|
||||||
|
Download Cache
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="w-full grow p-4 border-2 border-emerald-500/50 rounded-2xl mt-5 overflow-y-auto">
|
||||||
|
<table className="min-w-full text-white">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="p-2 border border-black">PSSH</th>
|
||||||
|
<th className="p-2 border border-black">KID</th>
|
||||||
|
<th className="p-2 border border-black">Key</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{cacheData.length > 0 ? (
|
||||||
|
cacheData.map((item, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td className="p-2 border border-black">{item.PSSH}</td>
|
||||||
|
<td className="p-2 border border-black">{item.KID}</td>
|
||||||
|
<td className="p-2 border border-black">{item.Key}</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan="3" className="p-2 border border-black text-center">
|
||||||
|
No data found
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Cache;
|
@ -1,10 +1,192 @@
|
|||||||
function HomePage () {
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { readTextFromClipboard } from '../Functions/ParseChallenge'
|
||||||
|
import { Helmet } from 'react-helmet'; // Import Helmet
|
||||||
|
|
||||||
|
function HomePage() {
|
||||||
|
const [pssh, setPssh] = useState('');
|
||||||
|
const [licurl, setLicurl] = useState('');
|
||||||
|
const [proxy, setProxy] = useState('');
|
||||||
|
const [headers, setHeaders] = useState('');
|
||||||
|
const [cookies, setCookies] = useState('');
|
||||||
|
const [data, setData] = useState('');
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
const bottomRef = useRef(null);
|
||||||
|
const messageRef = useRef(null); // Reference to result container
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
if (isVisible) {
|
||||||
|
setIsVisible(false);
|
||||||
|
}
|
||||||
|
setPssh('');
|
||||||
|
setLicurl('');
|
||||||
|
setProxy('');
|
||||||
|
setHeaders('');
|
||||||
|
setCookies('');
|
||||||
|
setData('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitButton = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
fetch('/api/decrypt', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
pssh: pssh,
|
||||||
|
licurl: licurl,
|
||||||
|
proxy: proxy,
|
||||||
|
headers: headers,
|
||||||
|
cookies: cookies,
|
||||||
|
data: data
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const resultMessage = data['message'].replace(/\n/g, '<br />');
|
||||||
|
setMessage(resultMessage);
|
||||||
|
setIsVisible(true);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error during decryption request:', error);
|
||||||
|
setMessage('Error: Unable to process request.');
|
||||||
|
setIsVisible(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCopy = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (messageRef.current) {
|
||||||
|
const textToCopy = messageRef.current.innerText; // Grab the plain text (with visual line breaks)
|
||||||
|
navigator.clipboard.writeText(textToCopy).catch(err => {
|
||||||
|
alert('Failed to copy!');
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleFetchPaste = () => {
|
||||||
|
event.preventDefault();
|
||||||
|
readTextFromClipboard().then(() => {
|
||||||
|
setPssh(document.getElementById("pssh").value);
|
||||||
|
setLicurl(document.getElementById("licurl").value);
|
||||||
|
setHeaders(document.getElementById("headers").value);
|
||||||
|
setData(document.getElementById("data").value);
|
||||||
|
}).catch(err => {
|
||||||
|
alert('Failed to paste from fetch!');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isVisible && bottomRef.current) {
|
||||||
|
bottomRef.current.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}, [message, isVisible]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-full overflow-y-auto p-4">
|
<>
|
||||||
|
<div className="flex flex-col w-full overflow-y-auto p-4 min-h-full">
|
||||||
|
<Helmet>
|
||||||
|
<title>CDRM-Project</title>
|
||||||
|
</Helmet>
|
||||||
|
<form className="flex flex-col w-full h-full bg-black/5 p-4 overflow-y-auto">
|
||||||
|
<label htmlFor="pssh" className="text-white w-8/10 self-center">PSSH: </label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="pssh"
|
||||||
|
className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
|
||||||
|
value={pssh}
|
||||||
|
onChange={(e) => setPssh(e.target.value)}
|
||||||
|
/>
|
||||||
|
<label htmlFor="licurl" className="text-white w-8/10 self-center">License URL: </label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="licurl"
|
||||||
|
className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
|
||||||
|
value={licurl}
|
||||||
|
onChange={(e) => setLicurl(e.target.value)}
|
||||||
|
/>
|
||||||
|
<label htmlFor="proxy" className="text-white w-8/10 self-center">Proxy: </label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="proxy"
|
||||||
|
className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
|
||||||
|
value={proxy}
|
||||||
|
onChange={(e) => setProxy(e.target.value)}
|
||||||
|
/>
|
||||||
|
<label htmlFor="headers" className="text-white w-8/10 self-center">Headers: </label>
|
||||||
|
<textarea
|
||||||
|
id="headers"
|
||||||
|
className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
|
||||||
|
value={headers}
|
||||||
|
onChange={(e) => setHeaders(e.target.value)}
|
||||||
|
/>
|
||||||
|
<label htmlFor="cookies" className="text-white w-8/10 self-center">Cookies: </label>
|
||||||
|
<textarea
|
||||||
|
id="cookies"
|
||||||
|
className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
|
||||||
|
value={cookies}
|
||||||
|
onChange={(e) => setCookies(e.target.value)}
|
||||||
|
/>
|
||||||
|
<label htmlFor="data" className="text-white w-8/10 self-center">Data: </label>
|
||||||
|
<textarea
|
||||||
|
id="data"
|
||||||
|
className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
|
||||||
|
value={data}
|
||||||
|
onChange={(e) => setData(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex flex-col lg:flex-row w-full self-center mt-5 items-center lg:justify-around lg:items-stretch">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="bg-sky-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate w-1/2"
|
||||||
|
onClick={handleSubmitButton}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
<button onClick={handleFetchPaste} className="bg-yellow-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate mt-5 w-1/2 lg:mt-0">
|
||||||
|
Paste from fetch
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="bg-red-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate mt-5 w-1/2 lg:mt-0"
|
||||||
|
onClick={handleReset}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isVisible && (
|
||||||
|
<div id="main_content" className="flex-col w-full h-full p-10 items-center justify-center self-center">
|
||||||
|
<div className="flex flex-col w-full h-full overflow-y-auto items-center">
|
||||||
|
<div className='w-8/10 grow p-4 text-white text-bold text-center text-xl md:text-3xl border-2 border-sky-500/25 rounded-xl bg-black/5'>
|
||||||
|
<p className="w-full border-b-2 border-white/75 pb-2">Results:</p>
|
||||||
|
<p
|
||||||
|
className="w-full grow pt-10 break-words overflow-y-auto"
|
||||||
|
ref={messageRef}
|
||||||
|
dangerouslySetInnerHTML={{ __html: message }}
|
||||||
|
/>
|
||||||
|
<div ref={bottomRef} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col lg:flex-row w-full self-center mt-5 items-center lg:justify-around lg:items-stretch">
|
||||||
|
<button
|
||||||
|
className="bg-green-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate w-1/2"
|
||||||
|
onClick={handleCopy}
|
||||||
|
>
|
||||||
|
Copy Results
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HomePage
|
export default HomePage;
|
||||||
|
158
cdrm-frontend/src/components/Pages/TestPlayer.jsx
Normal file
158
cdrm-frontend/src/components/Pages/TestPlayer.jsx
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import shaka from 'shaka-player';
|
||||||
|
import { Helmet } from 'react-helmet'; // Import Helmet
|
||||||
|
|
||||||
|
function TestPlayer() {
|
||||||
|
const [mpdUrl, setMpdUrl] = useState(''); // State to hold the MPD URL
|
||||||
|
const [kids, setKids] = useState(''); // State to hold KIDs (separated by line breaks)
|
||||||
|
const [keys, setKeys] = useState(''); // State to hold Keys (separated by line breaks)
|
||||||
|
const [headers, setHeaders] = useState(''); // State to hold request headers
|
||||||
|
|
||||||
|
const videoRef = useRef(null); // Ref for the video element
|
||||||
|
const playerRef = useRef(null); // Ref for Shaka Player instance
|
||||||
|
|
||||||
|
// Function to update the MPD URL state
|
||||||
|
const handleInputChange = (event) => {
|
||||||
|
setMpdUrl(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to update KIDs and Keys
|
||||||
|
const handleKidsChange = (event) => {
|
||||||
|
setKids(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeysChange = (event) => {
|
||||||
|
setKeys(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHeadersChange = (event) => {
|
||||||
|
setHeaders(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to initialize Shaka Player
|
||||||
|
const initializePlayer = () => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
// Initialize Shaka Player only if it's not already initialized
|
||||||
|
if (!playerRef.current) {
|
||||||
|
const player = new shaka.Player(videoRef.current);
|
||||||
|
playerRef.current = player;
|
||||||
|
|
||||||
|
// Add error listener
|
||||||
|
player.addEventListener('error', (event) => {
|
||||||
|
console.error('Error code', event.detail.code, 'object', event.detail);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to handle submit and configure player with DRM keys and headers
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (mpdUrl && kids && keys) {
|
||||||
|
// Split the KIDs and Keys by new lines
|
||||||
|
const kidsArray = kids.split("\n").map((k) => k.trim());
|
||||||
|
const keysArray = keys.split("\n").map((k) => k.trim());
|
||||||
|
|
||||||
|
if (kidsArray.length !== keysArray.length) {
|
||||||
|
console.error("The number of KIDs and Keys must be the same.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Shaka Player only when the submit button is pressed
|
||||||
|
const player = new shaka.Player(videoRef.current);
|
||||||
|
|
||||||
|
// Widevine DRM configuration with the provided KIDs and Keys
|
||||||
|
const config = {
|
||||||
|
drm: {
|
||||||
|
clearKeys: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map KIDs to Keys
|
||||||
|
kidsArray.forEach((kid, index) => {
|
||||||
|
config.drm.clearKeys[kid] = keysArray[index];
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Configuring player with the following DRM config and headers:", config);
|
||||||
|
|
||||||
|
// Configure the player with ClearKey DRM and custom headers
|
||||||
|
player.configure(config);
|
||||||
|
|
||||||
|
// Load the video stream with MPD URL
|
||||||
|
player.load(mpdUrl).then(() => {
|
||||||
|
console.log('Video loaded');
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error('Error loading the video', error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('MPD URL, KIDs, and Keys are required.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load the video stream whenever the MPD URL changes
|
||||||
|
useEffect(() => {
|
||||||
|
initializePlayer(); // Initialize the player if it's not initialized already
|
||||||
|
}, []); // This effect runs only once on mount
|
||||||
|
|
||||||
|
// Helper function to parse headers from the textarea input
|
||||||
|
const parseHeaders = (headersText) => {
|
||||||
|
const headersArr = headersText.split('\n');
|
||||||
|
const headersObj = {};
|
||||||
|
headersArr.forEach((line) => {
|
||||||
|
const [key, value] = line.split(':');
|
||||||
|
if (key && value) {
|
||||||
|
headersObj[key.trim()] = value.trim();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return headersObj;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center w-full p-4">
|
||||||
|
<Helmet>
|
||||||
|
<title>Test Player</title>
|
||||||
|
</Helmet>
|
||||||
|
<div className="w-full flex flex-col">
|
||||||
|
<video
|
||||||
|
ref={videoRef}
|
||||||
|
width="100%"
|
||||||
|
height="auto"
|
||||||
|
controls
|
||||||
|
className="h-96"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={mpdUrl}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="MPD URL"
|
||||||
|
className="border-2 border-rose-700/50 mt-2 text-white p-1 rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
|
||||||
|
/>
|
||||||
|
<textarea
|
||||||
|
placeholder="KIDs (one per line)"
|
||||||
|
value={kids}
|
||||||
|
onChange={handleKidsChange}
|
||||||
|
className="border-2 border-rose-700/50 mt-2 text-white p-1 overflow-y-auto rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
|
||||||
|
/>
|
||||||
|
<textarea
|
||||||
|
placeholder="Keys (one per line)"
|
||||||
|
value={keys}
|
||||||
|
onChange={handleKeysChange}
|
||||||
|
className="border-2 border-rose-700/50 mt-2 text-white p-1 overflow-y-auto rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
|
||||||
|
/>
|
||||||
|
<textarea
|
||||||
|
placeholder="Headers (one per line)"
|
||||||
|
value={headers}
|
||||||
|
onChange={handleHeadersChange}
|
||||||
|
className="border-2 border-rose-700/50 mt-2 text-white p-1 overflow-y-auto rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
className="mt-4 p-2 bg-blue-500 text-white rounded"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TestPlayer;
|
@ -1 +1,10 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
details summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
details summary {
|
||||||
|
list-style: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user