DRM:HTML5
DRM으로 보호된 동영상 파일을 HTML5(즉 웹브라우저)로 재생하는 방법에 대한 정리.
ChatGPT 답변
const videoElement = document.getElementById('encryptedVideo');
const videoSource = 'https://example.com/encrypted-video.mp4';
fetch(videoSource)
.then(response => response.arrayBuffer())
.then(videoData => {
const videoContent = new Uint8Array(videoData);
const videoConfig = [{
initDataTypes: ['cenc'],
videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}],
audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}]
}];
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', videoConfig)
.then(keySystemAccess => {
const keySystem = keySystemAccess.keySystem;
const keySession = keySystem.createSession('persistent-license');
keySession.generateRequest('cenc', videoContent).then(request => {
return fetch(videoSource + '.key', {
method: 'POST',
body: request,
headers: new Headers({
'Content-Type': 'application/octet-stream'
})
});
}).then(response => response.arrayBuffer())
.then(keyData => keySession.update(keyData))
.then(() => {
const decryptedStream = new MediaSource();
videoElement.src = URL.createObjectURL(decryptedStream);
const decryptedSourceBuffer = decryptedStream.addSourceBuffer('video/mp4; codecs="avc1.42E01E"');
const video = new Uint8Array(videoContent);
decryptedSourceBuffer.addEventListener('updateend', () => {
if (!decryptedSourceBuffer.updating && decryptedStream.readyState === 'open') {
decryptedSourceBuffer.appendBuffer(video);
}
});
});
});
});
위의 코드는 다음과 같은 과정을 거쳐 동작합니다.
- HTML5 Video 요소를 생성합니다.
- 동영상 파일을 가져와서 ArrayBuffer 형태로 변환합니다.
- CENC 암호화를 사용하여 보호된 동영상을 재생하기 위한 MediaKeySystemAccess 객체를 생성합니다.
- MediaKeySession 객체를 생성하고, CENC 요청을 생성하여 서버로 전송합니다.
- 서버에서는 요청을 받아 암호화된 키를 생성하고, ArrayBuffer 형태로 변환하여 응답합니다.
- MediaKeySession 객체를 사용하여 키를 업데이트합니다.
- MediaSource 객체를 생성하고, 비디오 스트림을 복호화하여 SourceBuffer에 추가합니다.
- SourceBuffer에 복호화된 비디오 스트림을 추가하여 동영상을 재생합니다.
m3u8 버전
const videoElement = document.getElementById('encryptedVideo');
const manifestUrl = 'https://example.com/manifest.m3u8';
fetch(manifestUrl)
.then(response => response.text())
.then(manifestData => {
const manifestLines = manifestData.trim().split('\n');
const manifestBaseUrl = manifestUrl.substring(0, manifestUrl.lastIndexOf('/') + 1);
const variantLines = manifestLines.filter(line => line.startsWith('#EXT-X-STREAM-INF'));
const variantUrls = variantLines.map(line => line.split('\n')[1].trim());
const keySystemConfig = [{
initDataTypes: ['cenc'],
videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}],
audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}]
}];
Promise.all(variantUrls.map(variantUrl => {
return fetch(manifestBaseUrl + variantUrl)
.then(response => response.text())
.then(variantData => {
const variantLines = variantData.trim().split('\n');
const mediaSegmentLines = variantLines.filter(line => line.startsWith('#EXTINF'));
const mediaSegmentUrls = mediaSegmentLines.map((line, index) => variantLines[index * 2 + 1]);
return Promise.all(mediaSegmentUrls.map(segmentUrl => {
return fetch(manifestBaseUrl + segmentUrl)
.then(response => response.arrayBuffer())
.then(segmentData => {
const segmentContent = new Uint8Array(segmentData);
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', keySystemConfig)
.then(keySystemAccess => {
const keySystem = keySystemAccess.keySystem;
const keySession = keySystem.createSession('persistent-license');
keySession.generateRequest('cenc', segmentContent).then(request => {
return fetch(manifestBaseUrl + segmentUrl + '.key', {
method: 'POST',
body: request,
headers: new Headers({
'Content-Type': 'application/octet-stream'
})
});
}).then(response => response.arrayBuffer())
.then(keyData => keySession.update(keyData))
.then(() => {
return segmentData;
});
});
});
}));
})
})).then(mediaSegments => {
const decryptedStreams = mediaSegments.map(segmentData => {
const segmentContent = new Uint8Array(segmentData);
const decryptedStream = new MediaSource();
const decryptedUrl = URL.createObjectURL(decryptedStream);
videoElement.src = decryptedUrl;
const decryptedSourceBuffer = decryptedStream.addSourceBuffer('video/mp4; codecs="avc1.42E01E"');
decryptedSourceBuffer.mode = 'segments';
decryptedSourceBuffer.appendBuffer(segmentContent);
return decryptedStream;
});
// Switch between media streams
let currentStreamIndex = 0;
const switchMediaStream = () => {
currentStreamIndex = (currentStreamIndex + 1) % decryptedStreams.length;
const decryptedStream = decryptedStreams[currentStreamIndex];
if (videoElement.src !== decryptedStream.url) {
videoElement.src = decryptedStream.url;
}
};
setInterval(switchMediaStream, 5000);
});
});
See also
- DRM
- HTML5
- Shaka Packager
- Shaka Player
- Widevine
- PlayReady
- Marlin
- CENC
- FairPlay Streaming
- AES Crypt
- requestMediaKeySystemAccess