DRM implementation with progressive-ts muxing

So we want to implement DRM on our current implementation of progressive-ts muxing. How can we go about it? We want to implement it on both android and iOS. Please find our current encoding code pasted below. Also, We are using m3u8 files on both android and iOS. To start encoding one needs to call encodeMedia function. On the player side, we are using exoplayer for android and AVQueuePlayer for iOS.If any more info is required please do let me know.

const BitmovinApi = require("@bitmovin/api-sdk").default;
const {
    AacAudioConfiguration,
    AclEntry,
    AclPermission,
    AudioMediaInfo,
    CloudRegion,
    Encoding,
    EncodingOutput,
    GcsInput,
    GcsOutput,
    HlsManifest,
    HlsVersion,
    MuxingStream,
    ProgressiveTsMuxing,
    Stream,
    StreamInput,
    StreamSelectionMode,
} = require('@bitmovin/api-sdk');

const createInputGCSHandle = (bitmovinApi, gcsHandleName, bucketName) => {
    return bitmovinApi.encoding.inputs.gcs.create(
        new GcsInput({
            name: gcsHandleName,
            accessKey: process.env.GCS_ACCESS_KEY,
            secretKey: process.env.GCS_SECRET_KEY,
            bucketName: bucketName,
        })
    );
}

const createOutputGCSHandle = (bitmovinApi, gcsHandleName, bucketName) => {
    return bitmovinApi.encoding.outputs.gcs.create(
        new GcsInput({
            name: gcsHandleName,
            accessKey: process.env.GCS_ACCESS_KEY,
            secretKey: process.env.GCS_SECRET_KEY,
            bucketName: bucketName,
        })
    );
}

const getEncodingHandle = (bitmovinApi, encodingHandleName) => {
    return bitmovinApi.encoding.encodings.create(
        new Encoding({
            name: encodingHandleName,
            cloudRegion: CloudRegion.GOOGLE_ASIA_SOUTHEAST_1
        })
    );
}


const createCodecConfigHandle = (bitmovinApi, audioConfigName, bitrateValue) => {
    return bitmovinApi.encoding.configurations.audio.aac.create(
        new AacAudioConfiguration({
            name: audioConfigName,
            bitrate: bitrateValue
        })
    );
}

const createStreamInputObject = (gcsId, inputPath) => {
    return new StreamInput({
        inputId: gcsId,
        inputPath: inputPath,
        selectionMode: StreamSelectionMode.AUTO
    });
}

const createAudioStreamHandle = (bitmovinApi, encodingHandleId, audioCodecConfigurationId, audioStreamInput) => {
    return bitmovinApi.encoding.encodings.streams.create(
        encodingHandleId,
        new Stream({
            codecConfigId: audioCodecConfigurationId,
            inputStreams: [audioStreamInput]
        })
    );
}

const createAudioMuxingHandle = (bitmovinApi, encodingHandleId, fileName, audioStreamHandleId, gcsOutputHandleId, fileOutputPath) => {
    // const segmentLength = 10;// 0
    // const segmentNaming = 'seg_%number%.ts';
    return bitmovinApi.encoding.encodings.muxings.progressiveTs.create(
        encodingHandleId,
        new ProgressiveTsMuxing({
            // segmentLength: segmentLength,
            // segmentNaming: segmentNaming,
            filename: fileName,
            streams: [new MuxingStream({ streamId: audioStreamHandleId })],
            outputs: [
                new EncodingOutput({
                    outputId: gcsOutputHandleId,
                    outputPath: fileOutputPath || '/',
                    acl: [new AclEntry({
                        permission: AclPermission.PUBLIC_READ
                    })]
                })
            ]
        })
    );
}
const createHlsManifest = (bitmovinApi, handleName, fileName, gcsOutputHandleId, fileOutputPath) => {
    return bitmovinApi.encoding.manifests.hls.create(
        new HlsManifest({
            name: handleName,
            manifestName: fileName,
            hlsMediaPlaylistVersion: HlsVersion.HLS_V4,
            hlsMasterPlaylistVersion: HlsVersion.HLS_V4,
            outputs: [
                new EncodingOutput({
                    outputId: gcsOutputHandleId,
                    outputPath: fileOutputPath || '/',
                    acl: [new AclEntry({
                        permission: AclPermission.PUBLIC_READ
                    })]
                })
            ]
        })
    );
}

exports.getCurrentStatus = async (encodingId) => {
    const bitmovinApi = new BitmovinApi({
        apiKey: process.env.BITMOVIN_API_KEY
    });
    return await bitmovinApi.encoding.encodings.status(encodingId)
}

exports.encodeMedia = async (filePath, outputPath, bucketName) => {
    const inputPath = filePath;
    const fileName = filePath.replace(/^.*[\\\/]/, '').split('.')[0];

    const bitmovinApi = new BitmovinApi({
        apiKey: process.env.BITMOVIN_API_KEY
    });

    try {
        console.info('encodeMedia', 'Started encoding file:', inputPath, 'For output:', outputPath, 'For bucket', bucketName);

        const gcsInputHandle = await createInputGCSHandle(bitmovinApi, 'GCS_INPUT', bucketName);
        console.info('encodeMedia', `Created Input GCS`);

        const encodingHandle = await getEncodingHandle(bitmovinApi, 'MEDIA_ENCODER')
        console.info('encodeMedia', 'Created encoding instance');

        const gcsOutputHandle = await createOutputGCSHandle(bitmovinApi, 'GCS_OUTPUT', bucketName);
        console.info('encodeMedia', 'Created Output GCS');


        console.debug('encodeMedia', 'Bitrates', process.env.BITRATE_LOW, process.env.BITRATE_MED, process.env.BITRATE_HIGH);
        const audioCodecConfigurationLow = await createCodecConfigHandle(bitmovinApi, 'AUDIO_CODEC_LOW', process.env.BITRATE_LOW);
        const audioCodecConfigurationMed = await createCodecConfigHandle(bitmovinApi, 'AUDIO_CODEC_MED', process.env.BITRATE_MED);
        const audioCodecConfigurationHigh = await createCodecConfigHandle(bitmovinApi, 'AUDIO_CODEC_HIGH', process.env.BITRATE_HIGH);
        console.info('encodeMedia', 'Created audioCode configs');


        const audioStreamInputLow = createStreamInputObject(gcsInputHandle.id, inputPath);
        const audioStreamInputMed = createStreamInputObject(gcsInputHandle.id, inputPath);
        const audioStreamInputHigh = createStreamInputObject(gcsInputHandle.id, inputPath);
        console.info('encodeMedia', 'Created Audio stream Input');

        const audioStreamHandleLow = await createAudioStreamHandle(bitmovinApi,
            encodingHandle.id,
            audioCodecConfigurationLow.id,
            audioStreamInputLow);
        const audioStreamHandleMed = await createAudioStreamHandle(bitmovinApi,
            encodingHandle.id,
            audioCodecConfigurationMed.id,
            audioStreamInputMed);
        const audioStreamHandleHigh = await createAudioStreamHandle(bitmovinApi,
            encodingHandle.id,
            audioCodecConfigurationHigh.id,
            audioStreamInputHigh);
        console.info('encodeMedia', 'Created Audio stream Handle');


        const lowAudioMuxingHandle = await createAudioMuxingHandle(
            bitmovinApi,
            encodingHandle.id,
            `${fileName}Low.ts`,
            audioStreamHandleLow.id,
            gcsOutputHandle.id,
            outputPath);
        const medAudioMuxingHandle = await createAudioMuxingHandle(
            bitmovinApi,
            encodingHandle.id,
            `${fileName}Med.ts`,
            audioStreamHandleMed.id,
            gcsOutputHandle.id,
            outputPath);
        const highAudioMuxingHandle = await createAudioMuxingHandle(
            bitmovinApi,
            encodingHandle.id,
            `${fileName}High.ts`,
            audioStreamHandleHigh.id,
            gcsOutputHandle.id,
            outputPath);
        console.info('encodeMedia', 'Audio Muxing handle created');

        console.info('encodeMedia', 'Encoding started');
        await bitmovinApi.encoding.encodings.start(encodingHandle.id);
        console.info('encodeMedia', 'Encoding Ended');

        const manifest = await createHlsManifest(bitmovinApi,
            'MANIFEST_FILE',
            `${fileName}.m3u8`,
            gcsOutputHandle.id,
            outputPath);


        const audioMediaInfoLow = new AudioMediaInfo({
            name: 'my-audio-media-low',
            groupId: 'audio_group_low',
            segmentPath: '',
            uri: `${fileName}Low.m3u8`,
            encodingId: encodingHandle.id,
            streamId: audioStreamHandleLow.id,
            muxingId: lowAudioMuxingHandle.id,
            language: 'en',
            autoselect: true,
            isDefault: true
        });
        const audioMediaInfoMed = new AudioMediaInfo({
            name: 'my-audio-media-med',
            groupId: 'audio_group_med',
            segmentPath: '',
            uri: `${fileName}Med.m3u8`,
            encodingId: encodingHandle.id,
            streamId: audioStreamHandleMed.id,
            muxingId: medAudioMuxingHandle.id,
            language: 'en',
            autoselect: true
        });
        const audioMediaInfoHigh = new AudioMediaInfo({
            name: 'my-audio-media-high',
            groupId: 'audio_group_high',
            segmentPath: '',
            uri: `${fileName}High.m3u8`,
            encodingId: encodingHandle.id,
            streamId: audioStreamHandleHigh.id,
            muxingId: highAudioMuxingHandle.id,
            language: 'en',
            autoselect: true
        });

        console.info('encodeMedia', 'Creating Manifest');
        await bitmovinApi.encoding.manifests.hls.media.audio.create(manifest.id, audioMediaInfoLow);
        await bitmovinApi.encoding.manifests.hls.media.audio.create(manifest.id, audioMediaInfoMed);
        await bitmovinApi.encoding.manifests.hls.media.audio.create(manifest.id, audioMediaInfoHigh);

        //await bitmovinApi.encoding.manifests.hls.start(manifest.id);
        console.info('encodeMedia', 'Manifest creation done');
        console.info('encodeMedia', 'Ended encoding.');

        return {
            manifestFilePath: `${outputPath}${manifest.manifestName}`,
            inputFilePath: inputPath,
            outputPath: outputPath,
            manfestFile: manifest.manifestName,
            encodingId: encodingHandle.id,
            manifestId: manifest.id
        };
    } catch (ex) {
        console.error('encodeMedia', 'Error while encoding', ex);
    }
}

exports.startManifestCreation = async (manifestId) => {

    const bitmovinApi = new BitmovinApi({
        apiKey: process.env.BITMOVIN_API_KEY
    });

    await bitmovinApi.encoding.manifests.hls.start(manifestId);
};

Hi @shubham.aggarwal,
In order to support Android & iOS, you will need to add Widevine (for Android) and Fairplay (for iOS) DRM. It’s very uncommon to do this with progressive-ts for Android/Widevine, I’m not even sure if Exoplayer supports this, and I don’t think the Bitmovin Encoding solution does.
Is there any technical reason why you try to achieve it this way? The recommended approach would be to use fMP4 instead. This can be used for both, Widevine/Android and Fairplay/iOS.

Please note that Widevine and Fairplay may require different (incompatible) encryption modes - Widevine historically used AES-128 CTR while Fairplay uses CBCS. Widevine did add CBCS support as well, but it’s only available in Android API level 25+. So if you can go with a single set of files with CBCS encryption mode depends on the Android devices you want or need to support. Please note that several other devices like SmartTVs often support it only at a later stage, e.g. Samsung Tizen supports CBCS on 2022+ models. Please make this decision also depending on your future device roadmap.
To support older devices, you will need two muxings, one with CBCS (Fairplay) and one with CTR (Widevine), which essentially means the content is duplicated in the end.

For the actual, detailed DRM implementation, it partly depends on your DRM provider and which interfaces they offer. See Bitmovin Docs - Encoding Tutorials | Integrating Bitmovin Encoder with PallyCon Multi-DRM for an example with the PallyCon DRM provider.

I’d also recommend checking out the CENC (Common Encryption) example in Javascript in our sample repository on Github, it creates content in fMP4 muxing with Widevine and Fairplay: bitmovin-api-sdk-examples/CencDrmContentProtection.ts at main · bitmovin/bitmovin-api-sdk-examples · GitHub

1 Like