Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Panasonic 2019 TVs #1226

Merged
merged 6 commits into from
Mar 2, 2023
Merged
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
151 changes: 99 additions & 52 deletions src/compat/browser_detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,71 +24,118 @@ interface IIE11Document extends Document {
documentMode? : unknown;
}

// true on IE11
// false on Edge and other IEs/browsers.
const isIE11 : boolean =
!isNode &&
typeof (window as IIE11WindowObject).MSInputMethodContext !== "undefined" &&
typeof (document as IIE11Document).documentMode !== "undefined";

// true for IE / Edge
const isIEOrEdge : boolean = isNode ?
false :
navigator.appName === "Microsoft Internet Explorer" ||
navigator.appName === "Netscape" &&
/(Trident|Edge)\//.test(navigator.userAgent);

const isEdgeChromium: boolean = !isNode &&
navigator.userAgent.toLowerCase().indexOf("edg/") !== -1;

const isFirefox : boolean = !isNode &&
navigator.userAgent.toLowerCase().indexOf("firefox") !== -1;

const isSamsungBrowser : boolean = !isNode &&
/SamsungBrowser/.test(navigator.userAgent);

const isTizen : boolean = !isNode &&
/Tizen/.test(navigator.userAgent);

const isWebOs : boolean = !isNode &&
navigator.userAgent.indexOf("Web0S") >= 0;

// Inspired form: http://webostv.developer.lge.com/discover/specifications/web-engine/
// Note: even that page doesn't correspond to what we've actually seen in the
// wild
const isWebOs2021 : boolean = isWebOs &&
(
/[Ww]eb[O0]S.TV-2021/.test(navigator.userAgent) ||
/[Cc]hr[o0]me\/79/.test(navigator.userAgent)
);
const isWebOs2022 : boolean = isWebOs &&
(
/[Ww]eb[O0]S.TV-2022/.test(navigator.userAgent) ||
/[Cc]hr[o0]me\/87/.test(navigator.userAgent)
);
/** Edge Chromium, regardless of the device */
let isEdgeChromium = false;

interface ISafariWindowObject extends Window {
safari? : { pushNotification? : { toString() : string } };
}
/** IE11, regardless of the device */
let isIE11 = false;

/** IE11 or Edge __Legacy__ (not Edge Chromium), regardless of the device */
let isIEOrEdge = false;

/** Firefox, regardless of the device */
let isFirefox = false;

/** `true` on Safari on a PC platform (i.e. not iPhone / iPad etc.) */
const isSafariDesktop : boolean =
!isNode && (
let isSafariDesktop = false;

/** `true` on Safari on an iPhone, iPad & iPod platform */
let isSafariMobile = false;

/** Samsung's own browser application */
let isSamsungBrowser = false;

/** `true` on devices where Tizen is the OS (e.g. Samsung TVs). */
let isTizen = false;

/** `true` on devices where WebOS is the OS (e.g. LG TVs). */
let isWebOs = false;

/** `true` specifically for WebOS 2021 version. */
let isWebOs2021 = false;

/** `true` specifically for WebOS 2022 version. */
let isWebOs2022 = false;

/** `true` for Panasonic devices. */
let isPanasonic = false;

((function findCurrentBrowser() : void {
if (isNode) {
return ;
}

// 1 - Find out browser between IE/Edge Legacy/Edge Chromium/Firefox/Safari

if (typeof (window as IIE11WindowObject).MSInputMethodContext !== "undefined" &&
typeof (document as IIE11Document).documentMode !== "undefined")
{
isIE11 = true;
isIEOrEdge = true;
} else if (
navigator.appName === "Microsoft Internet Explorer" ||
navigator.appName === "Netscape" &&
/(Trident|Edge)\//.test(navigator.userAgent)
) {
isIEOrEdge = true;
} else if (navigator.userAgent.toLowerCase().indexOf("edg/") !== -1) {
isEdgeChromium = true;
} else if (navigator.userAgent.toLowerCase().indexOf("firefox") !== -1) {
isFirefox = true;
} else if (typeof navigator.platform === "string" &&
/iPad|iPhone|iPod/.test(navigator.platform))
{
isSafariMobile = true;
} else if (
Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor") >= 0 ||
(window as ISafariWindowObject).safari?.pushNotification?.toString() ===
"[object SafariRemoteNotification]"
);
) {
isSafariDesktop = true;
}

/** `true` on Safari on an iPhone, iPad & iPod platform */
const isSafariMobile : boolean = !isNode &&
typeof navigator.platform === "string" &&
/iPad|iPhone|iPod/.test(navigator.platform);
// 2 - Find out specific device/platform information

// Samsung browser e.g. on Android
if (/SamsungBrowser/.test(navigator.userAgent)) {
isSamsungBrowser = true;
}

if (/Tizen/.test(navigator.userAgent)) {
isTizen = true;

// Inspired form: http://webostv.developer.lge.com/discover/specifications/web-engine/
// Note: even that page doesn't correspond to what we've actually seen in the
// wild
} else if (/[Ww]eb[O0]S/.test(navigator.userAgent)) {
isWebOs = true;

if (
/[Ww]eb[O0]S.TV-2022/.test(navigator.userAgent) ||
/[Cc]hr[o0]me\/87/.test(navigator.userAgent)
) {
isWebOs2022 = true;
} else if (
/[Ww]eb[O0]S.TV-2021/.test(navigator.userAgent) ||
/[Cc]hr[o0]me\/79/.test(navigator.userAgent)
) {
isWebOs2021 = true;
}
} else if (/[Pp]anasonic/.test(navigator.userAgent)) {
isPanasonic = true;
}
})());

interface ISafariWindowObject extends Window {
safari? : { pushNotification? : { toString() : string } };
}

export {
isEdgeChromium,
isIE11,
isIEOrEdge,
isFirefox,
isPanasonic,
isSafariDesktop,
isSafariMobile,
isSamsungBrowser,
Expand Down
7 changes: 5 additions & 2 deletions src/compat/can_reuse_media_keys.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { isWebOs } from "./browser_detection";
import {
isPanasonic,
isWebOs,
} from "./browser_detection";

/**
* Returns `true` if a `MediaKeys` instance (the `Encrypted Media Extension`
Expand All @@ -13,5 +16,5 @@ import { isWebOs } from "./browser_detection";
* @returns {boolean}
*/
export default function canReuseMediaKeys() : boolean {
return !isWebOs;
return !isWebOs && !isPanasonic;
}
101 changes: 61 additions & 40 deletions src/core/decrypt/__tests__/__global__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,48 +40,68 @@ import {
import { CancellationSignal } from "../../../../utils/task_canceller";

/** Default MediaKeySystemAccess configuration used by the RxPlayer. */
export const defaultKSConfig = [{
audioCapabilities: [ { contentType: "audio/mp4;codecs=\"mp4a.40.2\"" },
{ contentType: "audio/webm;codecs=opus" } ],
distinctiveIdentifier: "optional" as const,
initDataTypes: ["cenc"] as const,
persistentState: "optional" as const,
sessionTypes: ["temporary"] as const,
videoCapabilities: [ { contentType: "video/mp4;codecs=\"avc1.4d401e\"" },
{ contentType: "video/mp4;codecs=\"avc1.42e01e\"" },
{ contentType: "video/webm;codecs=\"vp8\"" } ],
}];
export const defaultKSConfig = [
{
audioCapabilities: [ { contentType: "audio/mp4;codecs=\"mp4a.40.2\"" },
{ contentType: "audio/webm;codecs=opus" } ],
distinctiveIdentifier: "optional" as const,
initDataTypes: ["cenc"] as const,
persistentState: "optional" as const,
sessionTypes: ["temporary"] as const,
videoCapabilities: [ { contentType: "video/mp4;codecs=\"avc1.4d401e\"" },
{ contentType: "video/mp4;codecs=\"avc1.42e01e\"" },
{ contentType: "video/webm;codecs=\"vp8\"" } ],
},
{
audioCapabilities: undefined,
distinctiveIdentifier: "optional" as const,
initDataTypes: ["cenc"] as const,
persistentState: "optional" as const,
sessionTypes: ["temporary"] as const,
videoCapabilities: undefined,
},
];

/**
* Default "com.microsoft.playready.recommendation" MediaKeySystemAccess
* configuration used by the RxPlayer.
*/
export const defaultPRRecommendationKSConfig = [{
audioCapabilities: [ { robustness: "3000",
contentType: "audio/mp4;codecs=\"mp4a.40.2\"" },
{ robustness: "3000",
contentType: "audio/webm;codecs=opus" },
{ robustness: "2000",
contentType: "audio/mp4;codecs=\"mp4a.40.2\"" },
{ robustness: "2000",
contentType: "audio/webm;codecs=opus" } ],
distinctiveIdentifier: "optional" as const,
initDataTypes: ["cenc"] as const,
persistentState: "optional" as const,
sessionTypes: ["temporary"] as const,
videoCapabilities: [ { robustness: "3000",
contentType: "video/mp4;codecs=\"avc1.4d401e\"" },
{ robustness: "3000",
contentType: "video/mp4;codecs=\"avc1.42e01e\"" },
{ robustness: "3000",
contentType: "video/webm;codecs=\"vp8\"" },
{ robustness: "2000",
contentType: "video/mp4;codecs=\"avc1.4d401e\"" },
{ robustness: "2000",
contentType: "video/mp4;codecs=\"avc1.42e01e\"" },
{ robustness: "2000",
contentType: "video/webm;codecs=\"vp8\"" } ],
}];
export const defaultPRRecommendationKSConfig = [
{
audioCapabilities: [ { robustness: "3000",
contentType: "audio/mp4;codecs=\"mp4a.40.2\"" },
{ robustness: "3000",
contentType: "audio/webm;codecs=opus" },
{ robustness: "2000",
contentType: "audio/mp4;codecs=\"mp4a.40.2\"" },
{ robustness: "2000",
contentType: "audio/webm;codecs=opus" } ],
distinctiveIdentifier: "optional" as const,
initDataTypes: ["cenc"] as const,
persistentState: "optional" as const,
sessionTypes: ["temporary"] as const,
videoCapabilities: [ { robustness: "3000",
contentType: "video/mp4;codecs=\"avc1.4d401e\"" },
{ robustness: "3000",
contentType: "video/mp4;codecs=\"avc1.42e01e\"" },
{ robustness: "3000",
contentType: "video/webm;codecs=\"vp8\"" },
{ robustness: "2000",
contentType: "video/mp4;codecs=\"avc1.4d401e\"" },
{ robustness: "2000",
contentType: "video/mp4;codecs=\"avc1.42e01e\"" },
{ robustness: "2000",
contentType: "video/webm;codecs=\"vp8\"" } ],
},
{
audioCapabilities: undefined,
distinctiveIdentifier: "optional" as const,
initDataTypes: ["cenc"] as const,
persistentState: "optional" as const,
sessionTypes: ["temporary"] as const,
videoCapabilities: undefined,
},
];

/** Default Widevine MediaKeySystemAccess configuration used by the RxPlayer. */
export const defaultWidevineConfig = (() => {
Expand All @@ -104,9 +124,10 @@ export const defaultWidevineConfig = (() => {
{ contentType: "audio/webm;codecs=opus",
robustness } ];
});
return defaultKSConfig.map(conf => {
return { ...conf, audioCapabilities, videoCapabilities };
});
return [
{ ...defaultKSConfig[0], audioCapabilities, videoCapabilities },
defaultKSConfig[1],
];
})();

/**
Expand Down
71 changes: 36 additions & 35 deletions src/core/decrypt/find_key_system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ function buildKeySystemConfigurations(
if (keySystem.distinctiveIdentifierRequired === true) {
distinctiveIdentifier = "required";
}
const { EME_DEFAULT_WIDEVINE_ROBUSTNESSES,
const { EME_DEFAULT_AUDIO_CODECS,
EME_DEFAULT_VIDEO_CODECS,
EME_DEFAULT_WIDEVINE_ROBUSTNESSES,
EME_DEFAULT_PLAYREADY_ROBUSTNESSES } = config.getCurrent();

// Set robustness, in order of consideration:
Expand Down Expand Up @@ -206,42 +208,41 @@ function buildKeySystemConfigurations(
// https://www.w3.org/TR/encrypted-media/#get-supported-configuration-and-consent

const videoCapabilities: IMediaCapability[] =
flatMap(videoRobustnesses, (robustness) =>
["video/mp4;codecs=\"avc1.4d401e\"",
"video/mp4;codecs=\"avc1.42e01e\"",
"video/webm;codecs=\"vp8\""].map(contentType => {
return robustness !== undefined ? { contentType, robustness } :
{ contentType };
}));
flatMap(videoRobustnesses, (robustness) => {
return EME_DEFAULT_VIDEO_CODECS.map(contentType => {
return robustness === undefined ? { contentType } :
{ contentType, robustness };
});
});

const audioCapabilities: IMediaCapability[] =
flatMap(audioRobustnesses, (robustness) =>
["audio/mp4;codecs=\"mp4a.40.2\"",
"audio/webm;codecs=opus"].map(contentType => {
return robustness !== undefined ? { contentType, robustness } :
{ contentType };
}));

// TODO Re-test with a set contentType but an undefined robustness on the
// STBs on which this problem was found.
//
// add another with no {audio,video}Capabilities for some legacy browsers.
// As of today's spec, this should return NotSupported but the first
// candidate configuration should be good, so we should have no downside
// doing that.
// initDataTypes: ["cenc"],
// videoCapabilities: undefined,
// audioCapabilities: undefined,
// distinctiveIdentifier,
// persistentState,
// sessionTypes,

return [{ initDataTypes: ["cenc"],
videoCapabilities,
audioCapabilities,
distinctiveIdentifier,
persistentState,
sessionTypes }];
flatMap(audioRobustnesses, (robustness) => {
return EME_DEFAULT_AUDIO_CODECS.map(contentType => {
return robustness === undefined ? { contentType } :
{ contentType, robustness };
});
});

const wantedMediaKeySystemConfiguration : MediaKeySystemConfiguration = {
initDataTypes: ["cenc"],
videoCapabilities,
audioCapabilities,
distinctiveIdentifier,
persistentState,
sessionTypes,
};

return [
wantedMediaKeySystemConfiguration,

// Some legacy implementations have issues with `audioCapabilities` and
// `videoCapabilities`, so we're including a supplementary
// `MediaKeySystemConfiguration` without those properties.
{ ...wantedMediaKeySystemConfiguration,
audioCapabilities: undefined ,
videoCapabilities: undefined,
} as unknown as MediaKeySystemConfiguration,
];
}

/**
Expand Down
Loading