video-dev / hls.js

HLS.js is a JavaScript library that plays HLS in browsers with support for MSE.
https://hlsjs.video-dev.org/demo
Other
14.88k stars 2.58k forks source link

customization of key data before binding to segments? #3365

Closed akshaybhuradia2020 closed 3 years ago

akshaybhuradia2020 commented 3 years ago

What do you want to do with Hls.js? I have build key server for encrypting and decrypting HLS content. All key are stored in encrypted form. I need to decrypt key first which is get from key server and then start encryption or decryption(playback).

I have made changes in code of hls.js before attaching key to segment, i want to decrypt encrypted key and then bind to correspond segment. first crypto.js module is not importing and second most important i want to add property decrypt key property to hls object, so i can access this property in fragment_createClass() function(or there is any other way around to access?).

What have you tried so far?

import { Component, OnInit, ViewChild, ElementRef, AfterViewInit, OnChanges } from '@angular/core';
import Hls from '../../../node_modules/hls.js/dist/hls.js';

@Component({
  selector: 'app-playerdemo',
  templateUrl: './playerdemo.component.html',
  styleUrls: ['./playerdemo.component.css']
})
export class PlayerdemoComponent implements OnInit, AfterViewInit, OnChanges {
  @ViewChild("myplayer", { static: false }) videoElement: ElementRef;
  constructor() { }

  bash : any;
  ngOnInit() {}

  ngOnChanges() {
    this.bash.on(Hls.Events.MEDIA_ATTACHED, function () {
      console.log('video and hls.js are now bound together !');
      this.videoElement.nativeElement.play();
    });

    this.bash.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
          console.log("manifest loaded, found " + data.levels.length + " quality level");
    });
  }

  ngAfterViewInit() {
    this.bash = new Hls({debug: true, decryptkey : '0B7819C1157644EFB06BEAE5653D34D2'}); // 'how can i access this decryptkey property in below code'
    // console.log(this.bash);
    this.bash.attachMedia(this.videoElement.nativeElement);
    this.bash.loadSource('http://x.x.x.x:xxx/live/1080p.m3u8');
    // this.bash.on(Hls.Events.KEY_LOADING, function(event, data){
    //   console.log(data);
    // })

    // this.bash.on(Hls.Events.KEY_LOADED, function(event, data){
    //   console.log(data);
    // })
  }

}

Code start from line 6999

  {
    key: "decryptdata",
    get: function get() {
      if (!this.levelkey && !this._decryptdata) {
        return null;
      }

      if (!this._decryptdata && this.levelkey) {
        var sn = this.sn;
        if(this.levelkey.key !== null){
          let encodedword = new TextDecoder().decode(this.levelkey.key)
          let CryptoJS = require("crypto-js"); 
          let bytes  = CryptoJS.AES.decrypt(encodedword, CryptoJS.enc.Hex.parse(<decrypt key property i have defined above>), {iv: CryptoJS.enc.Hex.parse('00000000000000000000000000000000')});
          this.levelkey.key = new TextEncoder().encode(CryptoJS.enc.Utf8.stringify(bytes));
        }

        if (typeof sn !== 'number') {
          // We are fetching decryption data for a initialization segment
          // If the segment was encrypted with AES-128
          // It must have an IV defined. We cannot substitute the Segment Number in.
          if (this.levelkey && this.levelkey.method === 'AES-128' && !this.levelkey.iv) {
            logger["logger"].warn("missing IV for initialization segment with method=\"" + this.levelkey.method + "\" - compliance issue");
          }
          /*
          Be converted to a Number.
          'initSegment' will become NaN.
          NaN, which when converted through ToInt32() -> +0.
          ---
          Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
          */

          sn = 0;
        }

        this._decryptdata = this.setDecryptDataFromLevelKey(this.levelkey, sn);
      }
      return this._decryptdata;
    }

Error when import crypto.js in above code.

image

Note : there is any other way around?, so that i can use crypt folder aes-decryptor.js for key decryption.

Debug Error :

{type: "mediaError", details: "fragParsingError", fatal: false, reason: "no audio/video samples found", frag: Fragment, …}
details: "fragParsingError"
fatal: false
frag: Fragment {_url: "http://x.x.x.x:xxx/1080p_068.ts", _byteRange: null, _decryptdata: LevelKey, _elementaryStreams: {…}, deltaPTS: 0, …}
id: "main"
reason: "no audio/video samples found"
type: "mediaError"
__proto__: Object
akshaybhuradia2020 commented 3 years ago

I have made changes in code but hls content is not playing.

Changes i made from above reference :

1) import crypto-js module at top of hls.js

var CryptoJS = require('crypto-js');
typeof window !== "undefined" &&
(function webpackUniversalModuleDefinition(root, factory) {
    if(typeof exports === 'object' && typeof module === 'object')
        module.exports = factory();
    else if(typeof define === 'function' && define.amd)
        define([], factory);
    else if(typeof exports === 'object')
        exports["Hls"] = factory();

2)Add deckey property in hls object

////
  ngAfterViewInit() {
    this.bash = new Hls({debug: false, deckey: '0B7819C1157644EFB06BEAE5653D34D2'});
    this.bash.attachMedia(this.videoElement.nativeElement);
    this.bash.loadSource('http://x.x.x.x:xxx/1080p.m3u8');

    // this.bash.on(Hls.Events.KEY_LOADING, function(event, data){
    //   console.log(data);
    // })

    // this.bash.on(Hls.Events.KEY_LOADED, function(event, data){
    //   console.log(data);
    // })
  }
////

////// changes parameter in m3u8_parser_M3U8Parser.parseLevelPlaylist(hls.config.deckey) 
  _proto._handleTrackOrLevelPlaylist = function _handleTrackOrLevelPlaylist(response, stats, context, networkDetails) {
    var hls = this.hls;
    var id = context.id,
        level = context.level,
        type = context.type;
    var url = PlaylistLoader.getResponseUrl(response, context); // if the values are null, they will result in the else conditional
    var levelUrlId = Object(number["isFiniteNumber"])(id) ? id : 0;
    var levelId = Object(number["isFiniteNumber"])(level) ? level : levelUrlId;
    var levelType = PlaylistLoader.mapContextToLevelType(context);
    var levelDetails = m3u8_parser_M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, levelUrlId, hls.config.deckey); // set stats on level structure
    // TODO(jstackhouse): why? mixing concerns, is it just treated as value bag?

    levelDetails.tload = stats.tload;
//////

//////
added deckey property in Levelkey class
var level_key_LevelKey = /*#__PURE__*/function () {
  function LevelKey(baseURI, relativeURI) {
    this._uri = null;
    this.baseuri = void 0;
    this.reluri = void 0;
    this.method = null;
    this.key = null;
    this.iv = null;
    this.baseuri = baseURI;
    this.reluri = relativeURI;
    this.deckey = null;
  }
//////

/////  set deckey property during m3u8 parsing////
          case 'KEY':
            {
              // https://tools.ietf.org/html/rfc8216#section-4.3.2.4
              var decryptparams = value1;
              var keyAttrs = new attr_list(decryptparams);
              var decryptmethod = keyAttrs.enumeratedString('METHOD');
              var decrypturi = keyAttrs.URI;
              var decryptiv = keyAttrs.hexadecimalInteger('IV'); // From RFC: This attribute is OPTIONAL; its absence indicates an implicit value of "identity".

              var decryptkeyformat = keyAttrs.KEYFORMAT || 'identity';

              if (decryptkeyformat === 'com.apple.streamingkeydelivery') {
                logger["logger"].warn('Keyformat com.apple.streamingkeydelivery is not supported');
                continue;
              }

              if (decryptmethod) {
                levelkey = new level_key_LevelKey(baseurl, decrypturi);
                if (decrypturi && ['AES-128', 'SAMPLE-AES', 'SAMPLE-AES-CENC'].indexOf(decryptmethod) >= 0) {
                  levelkey.method = decryptmethod;
                  levelkey.key = null; // Initialization Vector (IV)
                  levelkey.iv = decryptiv;
                  levelkey.deckey = deckey;
                }
              }

              break;
//////

//////  changes according to my use case /////
    key: "decryptdata",
    get: function get() {

      if (!this.levelkey && !this._decryptdata) {
        return null;
      }
      if (!this._decryptdata && this.levelkey) {
        var sn = this.sn;
        console.log(this.levelkey.key, sn);
        if(this.levelkey.key !== null) {

          if(this.levelkey.key.byteLength === 16) {
            console.log(this.levelkey.key, sn);
          }
          else if(this.levelkey.key.byteLength > 16) {
            let bytes = CryptoJS.AES.decrypt(new TextDecoder().decode(this.levelkey.key).trim(), CryptoJS.enc.Hex.parse(this.levelkey.deckey), {iv: CryptoJS.enc.Hex.parse('00000000000000000000000000000000')});
            this.levelkey.key = new TextEncoder().encode(CryptoJS.enc.Utf8.stringify(bytes).trim());
            console.log(this.levelkey.key, sn);
          }

        }
/////

Debug Error

{type: "mediaError", details: "fragParsingError", fatal: false, reason: "no audio/video samples found", frag: Fragment, …}
details: "fragParsingError"
fatal: false
frag: Fragment {_url: "http://x.x.x.x:xxx/1080p_068.ts", _byteRange: null, _decryptdata: LevelKey, _elementaryStreams: {…}, deltaPTS: 0, …}
id: "main"
reason: "no audio/video samples found"
type: "mediaError"
__proto__: Object

NOTE:

I am not getting any error and all chunks are loaded. But hls content is not playing. image

encrypted key in KEY_URI image

tcyeee commented 1 year ago

Regarding this issue, have you finally found a solution?

tcyeee commented 1 year ago

I encountered the same problem as you did. I tried to replace it when loading the 'key' again, but as you know, it didn't work at all. So I want to know what your final solution was. 🙏

    hls.on(Hls.Events.KEY_LOADING, function(event, data) {
        var key = 'new-key';
        data.frag.decryptdata.key = new Uint8Array(Array.from(key, c => c.charCodeAt(0)));
    });