Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2d4abc8
Changes for MK Player UI, temoporary comit before tryin iphone fix
binon Sep 4, 2024
f5fff25
Fix for iphone, need to test resource page
binon Sep 9, 2024
1779430
Updating MKPlayer controlbar
binon Sep 12, 2024
bafb166
New player UI, sorted play pause issuse
binon Sep 13, 2024
05e2515
MKPLayer implementation in Admin page
binon Sep 13, 2024
ca95e97
New MKPlayer Changes in Resource page
binon Sep 14, 2024
47663a9
New Player update in case/assement page
binon Sep 16, 2024
2fae38d
Fixed some of the resolution issues
binon Sep 16, 2024
47ae249
Merge pull request #582 from TechnologyEnhancedLearning/RC
binon Sep 16, 2024
9f054c7
Firefox fix
binon Sep 17, 2024
c019b3b
Added validation for video file
binon Sep 18, 2024
9d2e4d4
Video validaiton in landingpage
binon Sep 18, 2024
483b906
Proxy fix for iphone
binon Sep 25, 2024
7120d33
Updated package file to fix error in devops
binon Sep 25, 2024
a1c3ea1
MKIO iphone fix
binon Oct 3, 2024
9b8ecbe
Refactored the mediacontroller
binon Oct 3, 2024
907bdcd
Merge branch 'releases/tucana' into Develop/Features/TD-4294-Video_Au…
binon Oct 3, 2024
e53f6c8
Merge pull request #629 from TechnologyEnhancedLearning/Develop/Featu…
binon Oct 3, 2024
bd2693e
removed trailing comment code
binon Oct 3, 2024
580fde0
Merge pull request #630 from TechnologyEnhancedLearning/RC
binon Oct 3, 2024
3d098aa
Reverting package.json changes to see whether it fixes the devops build
binon Oct 4, 2024
9e6aabd
Updated package file
binon Oct 4, 2024
f2e08c1
Update package.json, concurrency back
binon Oct 4, 2024
fda4e3d
Disabling Source Maps to fix the memory issue
binon Oct 7, 2024
830a3c4
bumping up sass version
binon Oct 8, 2024
619e84c
Forcing the memory size from package.json
binon Oct 8, 2024
7dea8a6
Reverted the size option upgraded node version in package.json
binon Oct 8, 2024
029d2f8
copying RC package.lock json to see whether it fixes the devops build…
binon Oct 8, 2024
71c2fea
Pushing adminui package lock json
binon Oct 8, 2024
c9b2311
Implemented the AdminUI proxy for iPhone
binon Oct 10, 2024
5852223
Fixed video rendering issue on Iphone on landing page
binon Oct 11, 2024
f71a58c
Merge pull request #684 from TechnologyEnhancedLearning/RC
AnjuJose011 Oct 24, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,27 @@ public string Get(string playBackUrl, string token)
{
using (var reader = new StreamReader(stream))
{
const string qualityLevelRegex = @"(QualityLevels\(\d+\))";
const string qualityLevelRegex = @"(|)([^""\s]+\.m3u8\(encryption=cbc\))";
const string fragmentsRegex = @"(Fragments\([\w\d=-]+,[\w\d=-]+\))";
const string urlRegex = @"("")(https?:\/\/[\da-z\.-]+\.[a-z\.]{2,6}[\/\w \.-]*\/?[\?&][^&=]+=[^&=#]*)("")";
const string urlRegex = @"(https?:\/\/[\da-z\.-]+\.[a-z\.]{2,6}[\/\w \.-]*\?[^,\s""]*)";

var baseUrl = playBackUrl.Substring(0, playBackUrl.IndexOf(".ism", System.StringComparison.OrdinalIgnoreCase)) + ".ism";
this.logger.LogDebug($"baseUrl={baseUrl}");

var content = reader.ReadToEnd();

var newContent = Regex.Replace(content, urlRegex, string.Format(CultureInfo.InvariantCulture, "$1$2&token={0}$3", token));
content = ReplaceUrisWithProxy(content, baseUrl);
var newContent = Regex.Replace(content, urlRegex, match =>
{
string baseUrlWithQuery = match.Groups[1].Value; // URL including the query string

// Append the token correctly without modifying surrounding characters
string newUrl = baseUrlWithQuery.Contains("?") ?
$"{baseUrlWithQuery}&token={token}" :
$"{baseUrlWithQuery}?token={token}";

return newUrl;
});
this.logger.LogDebug($"newContent={newContent}");

var match = Regex.Match(playBackUrl, qualityLevelRegex);
Expand All @@ -87,5 +98,33 @@ public string Get(string playBackUrl, string token)

return null;
}

private static string ReplaceUrisWithProxy(string playlistContent, string proxyUrl)
{
// Split the playlist content into lines
var lines = playlistContent.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);

// Process each line to replace media or map URIs
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].StartsWith("#EXT-X-MAP:URI=", StringComparison.OrdinalIgnoreCase))
{
// Extract the URI from the current line for EXT-X-MAP
var existingUri = lines[i].Substring(lines[i].IndexOf('=') + 1).Trim('"');
var newUri = $"{proxyUrl}/{existingUri}";
lines[i] = lines[i].Replace(existingUri, newUri);
}
else if (lines[i].StartsWith("#EXTINF:", StringComparison.OrdinalIgnoreCase) && i + 1 < lines.Length)
{
// Get the URI from the next line for EXTINF
var existingUri = lines[i + 1].Trim();
var newUri = $"{proxyUrl}/{existingUri}";
lines[i + 1] = newUri;
}
}

// Join the modified lines back into a single string
return string.Join("\r\n", lines);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
import { AzureMediaAssetModel } from '../models/content/videoAssetModel';
import { MKPlayer } from '@mediakind/mkplayer';
import { MKPlayerType, MKStreamType } from '../MKPlayerConfigEnum';
//import { getPlayerConfig, getSourceConfig, initializePlayer } from '../mkiomediaplayer';
import { buildControlbar } from '../mkioplayer-controlbar';

export default Vue.extend({
props: {
Expand All @@ -89,14 +91,20 @@
player: null,
videoContainer: null,
mkioKey: '',
isIphone: false,
requestURL: ''
};
},
async created(): Promise<void> {
async created(): Promise<void> {
await this.getMKIOPlayerKey();
this.load();
this.getDisplayAVFlag();
this.getAudioVideoUnavailableView();
},
mounted() {
this.checkIfIphone();
this.requestURL = window.location.origin;
},
computed: {
getStyle() {
console.log("getLinkStyle", (this.pageSectionDetail || {}).draftHidden);
Expand Down Expand Up @@ -168,11 +176,37 @@
this.audioVideoUnavailableView = response;
});
},
//onSubtitleAdded() {
// this.player.subtitles.enable("subtitle" + this.section.id.toString());
//},
onPlayerReady() {
const videoElement = document.getElementById("bitmovinplayer-video-" + this.getPlayerUniqueId) as HTMLVideoElement;
if (videoElement) {
videoElement.controls = true;
}
var contanierId = this.section.id.toString();
var uniquePlayer = this.player;// (player_@Model.Id);
buildControlbar(contanierId, uniquePlayer);

// [BY] When we set UI to false we need to manually add the controls to the video element
//const videoElement = document.getElementById("bitmovinplayer-video-" + this.getPlayerUniqueId) as HTMLVideoElement;
//if (videoElement) {
// videoElement.controls = true;
//}

// var subtitleTrack;
//if (this.pageSectionDetail.videoAsset.azureMediaAsset && this.pageSectionDetail.videoAsset.closedCaptionsFile) {
// const captionsInfo = this.pageSectionDetail.videoAsset.closedCaptionsFile;
// var srcPath = "file/download/" + captionsInfo.filePath + "/" + captionsInfo.fileName;
// //srcPath = '@requestURL' + srcPath;
// srcPath = "https://bitdash-a.akamaihd.net/content/sintel/subtitles/subtitles_en.vtt";

// subtitleTrack = {
// id: "subtitle" + this.section.id.toString(),
// lang: "en",
// label: "english",
// url: srcPath,
// kind: "subtitle"
// };
//};

//this.player.addSubtitle(subtitleTrack);
},
async getMKIOPlayerKey(): Promise<void> {
this.mkioKey = await contentData.getMKPlayerKey();
Expand All @@ -189,14 +223,14 @@
// Grab the video container
this.videoContainer = document.getElementById(this.getPlayerUniqueId);

if(!this.mkioKey) {
if (!this.mkioKey) {
this.getMKIOPlayerKey();
}

// Prepare the player configuration
const playerConfig = {
key: this.mkioKey,
ui: false,
ui: true,
playback: {
muted: false,
autoplay: false,
Expand All @@ -205,15 +239,33 @@
theme: "dark",
events: {
ready: this.onPlayerReady,
//subtitleadded: this.onSubtitleAdded,
}
};

// Initialize the player with video container and player configuration
this.player = new MKPlayer(this.videoContainer, playerConfig);

var subtitleTrack = null;
var sectionId = this.section.id.toString();
if (this.pageSectionDetail.videoAsset.azureMediaAsset && this.pageSectionDetail.videoAsset.closedCaptionsFile) {
var captionsInfo = this.pageSectionDetail.videoAsset.closedCaptionsFile;;

if (captionsInfo) {
var srcPath = "/file/download/" + captionsInfo.filePath + "/" + captionsInfo.fileName;
subtitleTrack = {
id: "subtitle" + sectionId,
lang: "en",
label: "english",
url: this.requestURL + srcPath,
kind: "subtitle"
};
}
}
// Load source
const sourceConfig = {
hls: this.getMediaPlayUrl(this.pageSectionDetail.videoAsset.azureMediaAsset.locatorUri),
subtitleTracks: [subtitleTrack],
drm: {
clearkey: {
LA_URL: "HLS_AES",
Expand Down Expand Up @@ -282,8 +334,18 @@
},
getMediaPlayUrl(url: string): string {
let sourceUrl = url.substring(0, url.lastIndexOf("manifest")) + "manifest(format=m3u8-cmaf,encryption=cbc)";

if (this.isIphone) {
var token = this.pageSectionDetail.videoAsset.azureMediaAsset.authenticationToken;
sourceUrl = "/Media/MediaManifest?playBackUrl=" + sourceUrl + "&token=" + token;
}

return sourceUrl;
},
checkIfIphone() {
const userAgent = navigator.userAgent || navigator.vendor;
this.isIphone = /iPhone/i.test(userAgent);
},
},
watch: {
section() {
Expand Down Expand Up @@ -318,4 +380,8 @@
video[id^="bitmovinplayer-video"] {
width: 100%;
}

.bmpui-ui-controlbar .control-right {
float: right;
}
</style>
101 changes: 101 additions & 0 deletions AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/mkiomediaplayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { MKPlayer, MKPlayerConfig } from '@mediakind/mkplayer';
import { MKPlayerType, MKStreamType } from './MKPlayerConfigEnum';
interface ClearKeyConfig {
LA_URL: string;
headers: {
Authorization: string;
};
}

interface PlayerConfig {
key: string;
ui: boolean;
playback: {
muted: boolean;
autoplay: boolean;
preferredTech: Array<{ player: string; streaming: string }>;
};
theme: string;
events: {
ready: () => void;
};
}

interface SourceConfig {
hls: string;
drm: {
clearkey: ClearKeyConfig;
};
}

function getBearerToken(authenticationToken: string): string {
// Replace this with your actual logic to get the bearer token
return `Bearer ${authenticationToken}`;
}

function getPlayerConfig(
mkioKey: string,
onPlayerReady: () => void
): PlayerConfig {
return {
key: mkioKey,
ui: true,
playback: {
muted: false,
autoplay: false,
preferredTech: [{ player: "Html5", streaming: "Hls" }] // Adjust these strings if you have specific types
},
theme: "dark",
events: {
ready: onPlayerReady,
}
};
}

function getSourceConfig(
locatorUri: string,
authenticationToken: string
): SourceConfig {
return {
hls: locatorUri,
drm: {
clearkey: {
LA_URL: "HLS_AES",
headers: {
Authorization: getBearerToken(authenticationToken)
}
}
}
};
}

function initializePlayer(videoContainer: HTMLElement, playerConfig: MKPlayerConfig, playBackUrl: string, bearerToken: string): any {
const player = new MKPlayer(videoContainer, playerConfig);

var clearKeyConfig = {
//LA_URL: "https://ottapp-appgw-amp.prodc.mkio.tv3cloud.com/drm/clear-key?ownerUid=azuki",
LA_URL: "HLS_AES",
headers: {
"Authorization": bearerToken
}
};

const sourceConfig: SourceConfig = {
hls: playBackUrl,
drm: {
clearkey: clearKeyConfig
}
};

player.load(sourceConfig)
.then(() => {
console.log("Source loaded successfully!");
})
.catch(() => {
console.error("An error occurred while loading the source!");
});

return player;
};

export { getPlayerConfig, getSourceConfig, initializePlayer };
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Constructs and configures the control bar for the UI.
*
* This function performs the following tasks:
* 1. Selects the titlebar and controlbar elements from the DOM.
* 2. Creates a playback toggle button with an initial "Play" state and appends it to the controlbar.
* 3. Adds an event listener to the playback toggle button to handle play/pause functionality.
* 4. Retrieves all buttons from the titlebar, aligns them to the right (except for the "Mute" button), and appends them to the controlbar.
* 5. Selects the UI container element and sets up a MutationObserver to monitor changes in the container's class attribute.
* 6. Updates the playback toggle button state based on the player's state (playing or paused) when the container's class changes.
*/

function buildControlbar(id: string, player: { isPlaying: () => boolean; pause: () => void; play: () => void; }): void {
const mediacontainerId = 'videoContainer_' + id;

// Select the titlebar and controlbar elements from the DOM
const titlebar = document.querySelector(`#${mediacontainerId} .bmpui-ui-titlebar`) as HTMLElement;
const controlbar = document.querySelector(`#${mediacontainerId} .bmpui-ui-controlbar`) as HTMLElement;

// Check if both titlebar and controlbar elements exist
if (titlebar && controlbar) {

// Create a playback toggle button and set its initial state and appearance
const playbackToggleButton = document.createElement('button');
playbackToggleButton.classList.add('bmpui-ui-playbacktogglebutton', 'bmpui-off');
playbackToggleButton.setAttribute('aria-label', 'Play');
playbackToggleButton.innerHTML = '<span class="bmpui-label">Play</span>';
playbackToggleButton.id = 'playback-toggle-btn-' + id;
controlbar.appendChild(playbackToggleButton);

// Add an event listener to the playback toggle button
playbackToggleButton.addEventListener('click', function () {
// Toggle playback state based on the current state
if (player.isPlaying()) {
player.pause();
playbackToggleButton.classList.remove('bmpui-on');
playbackToggleButton.classList.add('bmpui-off');
playbackToggleButton.innerHTML = '<span class="bmpui-label">Play</span>';
} else {
player.play();
playbackToggleButton.classList.remove('bmpui-off');
playbackToggleButton.classList.add('bmpui-on');
playbackToggleButton.innerHTML = '<span class="bmpui-label">Pause</span>';
}
});

// Get all button elements from the titlebar
const buttons = titlebar.querySelectorAll('button');

// Reverse the button list and append each button to the controlbar
Array.from(buttons).reverse().forEach(button => {
if (button.textContent !== "Mute") {
button.classList.add('control-right'); // Add a class to align buttons to the right
}
controlbar.appendChild(button); // Append the button to the controlbar
});

// Select the UI container element
const uiOverlayElement = document.querySelector(`#${mediacontainerId} .bmpui-ui-playbacktoggle-overlay`) as HTMLElement;
uiOverlayElement.addEventListener('click', function () {
const uiContainerElement = document.querySelector(`#${mediacontainerId} .bmpui-ui-uicontainer`) as HTMLElement;
// Update the playback toggle button state based on the player's state
if (uiContainerElement.classList.contains('bmpui-player-state-playing')) {
playbackToggleButton.classList.remove('bmpui-on');
playbackToggleButton.classList.add('bmpui-off');
} else {
playbackToggleButton.classList.remove('bmpui-off');
playbackToggleButton.classList.add('bmpui-on');
}
});

} else {
console.error('UI container element not found');
}
}

export { buildControlbar };
Loading
Loading