Skip to content

Commit 5069859

Browse files
authored
Merge pull request #425 from TechnologyEnhancedLearning/CI
Merge AMS release changes to Test
2 parents 4001d30 + 9e2de89 commit 5069859

38 files changed

+3964
-34709
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ obj
4747
/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.jfm
4848
/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Database/LearningHub.Nhs.Migration.Staging.Database.dbmdl
4949
/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Database/LearningHub.Nhs.Migration.Staging.Database.jfm
50+
/LearningHub.Nhs.WebUI.AutomatedUiTests/appsettings.Development.json
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
namespace LearningHub.Nhs.AdminUI.Configuration
2+
{
3+
/// <summary>
4+
/// Config AzureMediaSettings.
5+
/// </summary>
6+
public class MediaKindSettings
7+
{
8+
/// <summary>
9+
/// Gets or sets subscription name.
10+
/// </summary>
11+
public string SubscriptionName { get; set; }
12+
13+
/// <summary>
14+
/// Gets or sets token.
15+
/// </summary>
16+
public string Token { get; set; }
17+
18+
/// <summary>
19+
/// Gets or sets storage media account name.
20+
/// </summary>
21+
public string StorageAccountName { get; set; }
22+
23+
/// <summary>
24+
/// Gets or sets media kind media service issuer.
25+
/// </summary>
26+
public string Issuer { get; set; }
27+
28+
/// <summary>
29+
/// Gets or sets media kind media service audience.
30+
/// </summary>
31+
public string Audience { get; set; }
32+
33+
/// <summary>
34+
/// Gets or sets the contentkey policyname.
35+
/// </summary>
36+
public string ContentKeyPolicyName { get; set; }
37+
38+
/// <summary>
39+
/// Gets or sets media kind media service jwt primary key secret.
40+
/// </summary>
41+
public string JWTPrimaryKeySecret { get; set; }
42+
43+
/// <summary>
44+
/// Gets or sets the media kind media kind MKPlayer licence key.
45+
/// </summary>
46+
public string MKPlayerLicence { get; set; }
47+
48+
/// <summary>
49+
/// Gets or sets the media kind blob connection string.
50+
/// </summary>
51+
public string MediaKindStorageConnectionString { get; set; }
52+
}
53+
}

AdminUI/LearningHub.Nhs.AdminUI/Configuration/WebSettings.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,5 +146,10 @@ public class WebSettings
146146
/// Gets or sets the FileUploadSettings.
147147
/// </summary>
148148
public FileUploadSettingsModel FileUploadSettings { get; set; } = new FileUploadSettingsModel();
149+
150+
/// <summary>
151+
/// Gets or sets the MediaKindSettings.
152+
/// </summary>
153+
public MediaKindSettings MediaKindSettings { get; set; } = new MediaKindSettings();
149154
}
150155
}

AdminUI/LearningHub.Nhs.AdminUI/Controllers/ResourceController.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,14 @@ public IActionResult GetAVUnavailableView()
346346
[HttpGet("GetDisplayAVFlag")]
347347
public bool GetDisplayAVFlag() => this.featureManager.IsEnabledAsync(FeatureFlags.DisplayAudioVideo).Result;
348348

349+
/// <summary>
350+
/// The GetMKPlayerKey.
351+
/// </summary>
352+
/// <returns>Mediakind MK Player Key.</returns>
353+
[Route("Resource/GetMKPlayerKey")]
354+
[HttpGet("GetMKPlayerKey")]
355+
public string GetMKPlayerKey() => this.websettings.Value.MediaKindSettings.MKPlayerLicence;
356+
349357
private static List<PagingOptionPair> FilterOptions()
350358
{
351359
List<PagingOptionPair> options = new List<PagingOptionPair>();

AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@
105105
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
106106
</PackageReference>
107107
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.0" />
108-
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
108+
<PackageReference Include="MK.IO" Version="1.6.0" />
109+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
109110
<PackageReference Include="NLog.Web.AspNetCore" Version="4.14.0" />
110111
<PackageReference Include="System.Data.SqlClient" Version="4.8.3" />
111112
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.14.1" />

AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/cmsPageRow.vue

Lines changed: 128 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
<h4 v-if="pageSectionDetail.sectionTitleElement=='h4'">{{pageSectionDetail.sectionTitle}}</h4>
1111
</div>
1212
</div>
13-
1413
<div :class="[`nhsuk-grid-row ${section.topMargin && (!pageSectionDetail || !pageSectionDetail.sectionTitle) ? 'information-page__row--padding-top' : '' } ${section.bottomMargin ? 'information-page__row--padding-bottom' : '' }`]">
1514

1615
<div v-if="sectionTemplateType === SectionTemplateType.Video" class="nhsuk-grid-column-two-thirds">
@@ -19,13 +18,15 @@
1918
</div>
2019
<div class="information-page__asset-container">
2120
<div id="mediaContainer" :class="[`${disableVideoControl ? 'videoControlDisabled' : ''}`]" v-show="sectionTemplateType === SectionTemplateType.Video && displayAVFlag" class="w-100">
22-
<video controls v-show="section.id" :id="[`azureMediaPlayer${section.id}`]"
21+
<!--<video controls v-show="section.id" :id="[`azureMediaPlayer${section.id}`]"
2322
data-setup='{"logo": { "enabled": false }, "techOrder": ["azureHtml5JS", "flashSS", "silverlightSS", "html5"], "nativeControlsForTouch": false, "fluid": true}'
2423
class="azuremediaplayer amp-default-skin amp-big-play-centered" style="height:250px;">
2524
<p class="amp-no-js">
2625
To view this media please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video
2726
</p>
28-
</video>
27+
</video>-->
28+
<div class="video-container" :id="getPlayerUniqueId"></div>
29+
2930
<div class="information-page__asset-link-container" v-if="pageSectionDetail && pageSectionDetail.videoAsset && pageSectionDetail.videoAsset.transcriptFile" :style="getTextBackbroundStyle">
3031
<a download :style="getLinkStyle" :href="[`/file/download/${pageSectionDetail.videoAsset.transcriptFile.filePath}/${pageSectionDetail.videoAsset.transcriptFile.fileName}`]">
3132
Download transcript
@@ -68,6 +69,7 @@
6869
import { PageSectionDetailModel, SectionLayoutType, } from '../models/content/pageSectionDetailModel';
6970
import { contentData } from '../data/content';
7071
import { AzureMediaAssetModel } from '../models/content/videoAssetModel';
72+
import { MKPlayer } from '@mediakind/mkplayer';
7173
7274
export default Vue.extend({
7375
props: {
@@ -82,10 +84,14 @@
8284
pageSectionDetail: null as PageSectionDetailModel,
8385
disableVideoControl: false,
8486
displayAVFlag: false,
85-
audioVideoUnavailableView : '' as string,
87+
audioVideoUnavailableView: '' as string,
88+
player: null,
89+
videoContainer: null,
90+
mkioKey: '',
8691
};
8792
},
88-
created() {
93+
async created(): Promise<void> {
94+
await this.getMKIOPlayerKey();
8995
this.load();
9096
this.getDisplayAVFlag();
9197
this.getAudioVideoUnavailableView();
@@ -145,19 +151,32 @@
145151
},
146152
isRightSectionLayout() {
147153
return this.section.sectionLayoutType == SectionLayoutType.Right;
148-
},
154+
},
155+
getPlayerUniqueId(): string {
156+
return `videoContainer_${this.section.id}`
157+
},
149158
},
150159
methods: {
151160
getDisplayAVFlag() {
152161
contentData.getDisplayAVFlag().then(response => {
153-
this.displayAVFlag = response;
154-
});
162+
this.displayAVFlag = response;
163+
});
155164
},
156165
getAudioVideoUnavailableView() {
157166
contentData.getAVUnavailableView().then(response => {
158-
this.audioVideoUnavailableView = response;
167+
this.audioVideoUnavailableView = response;
159168
});
160169
},
170+
onPlayerReady() {
171+
const videoElement = document.getElementById("bitmovinplayer-video-" + this.getPlayerUniqueId) as HTMLVideoElement;
172+
if (videoElement) {
173+
videoElement.controls = true;
174+
}
175+
},
176+
async getMKIOPlayerKey(): Promise<void> {
177+
this.mkioKey = await contentData.getMKPlayerKey();
178+
//return this.mkioKey;
179+
},
161180
load() {
162181
if (this.sectionTemplateType === SectionTemplateType.Video) {
163182
contentData.getPageSectionDetailVideo(this.section.id).then(response => {
@@ -166,56 +185,107 @@
166185
if (!this.pageSectionDetail.videoAsset)
167186
return;
168187
169-
const id = 'azureMediaPlayer' + this.pageSectionDetail.id;
170-
let azureMediaPlayer = amp(id);
188+
// Grab the video container
189+
this.videoContainer = document.getElementById(this.getPlayerUniqueId);
171190
172-
if (this.pageSectionDetail.videoAsset.azureMediaAsset) {
173-
$(`#${id}`).css({ 'height': '', 'border': '1px solid #768692' });
174-
this.disableVideoControl = false;
175-
} else {
176-
this.disableVideoControl = true;
191+
if(!this.mkioKey) {
192+
this.getMKIOPlayerKey();
177193
}
178194
179-
if (this.pageSectionDetail.videoAsset.thumbnailImageFile) {
180-
azureMediaPlayer.poster(`/file/download/${this.pageSectionDetail.videoAsset.thumbnailImageFile.filePath}/${this.pageSectionDetail.videoAsset.thumbnailImageFile.fileName}`);
181-
}
182-
if (this.pageSectionDetail.videoAsset.azureMediaAsset && this.pageSectionDetail.videoAsset.closedCaptionsFile) {
183-
azureMediaPlayer.src([{
184-
type: "application/vnd.ms-sstr+xml",
185-
src: this.pageSectionDetail.videoAsset.azureMediaAsset.locatorUri,
186-
protectionInfo: [{ type: 'AES', authenticationToken: `Bearer=${this.pageSectionDetail.videoAsset.azureMediaAsset.authenticationToken}` }]
187-
}],
188-
[{ kind: "captions", src: `/file/download/${this.pageSectionDetail.videoAsset.closedCaptionsFile.filePath}/${this.pageSectionDetail.videoAsset.closedCaptionsFile.fileName}`, srclang: "en", label: "english" }]);
189-
}
190-
else if (this.pageSectionDetail.videoAsset.azureMediaAsset && !this.pageSectionDetail.videoAsset.closedCaptionsFile) {
191-
azureMediaPlayer.src([{
192-
type: "application/vnd.ms-sstr+xml",
193-
src: this.pageSectionDetail.videoAsset.azureMediaAsset.locatorUri,
194-
protectionInfo: [{ type: 'AES', authenticationToken: `Bearer=${this.pageSectionDetail.videoAsset.azureMediaAsset.authenticationToken}` }]
195-
}]);
196-
}
195+
// Prepare the player configuration
196+
const playerConfig = {
197+
key: this.mkioKey,
198+
ui: false,
199+
playback: {
200+
muted: false,
201+
autoplay: false
202+
},
203+
theme: "dark",
204+
events: {
205+
ready: this.onPlayerReady,
206+
}
207+
};
208+
209+
// Initialize the player with video container and player configuration
210+
this.player = new MKPlayer(this.videoContainer, playerConfig);
211+
212+
// Load source
213+
const sourceConfig = {
214+
hls: this.getMediaPlayUrl(this.pageSectionDetail.videoAsset.azureMediaAsset.locatorUri),
215+
drm: {
216+
clearkey: {
217+
LA_URL: "HLS_AES",
218+
headers: {
219+
"Authorization": this.getBearerToken(this.pageSectionDetail.videoAsset.azureMediaAsset.authenticationToken)
220+
}
221+
}
222+
}
223+
};
224+
225+
this.player.load(sourceConfig)
226+
.then(() => {
227+
console.log("Source loaded successfully!");
228+
})
229+
.catch(() => {
230+
console.error("An error occurred while loading the source!");
231+
});
232+
233+
//const id = 'azureMediaPlayer' + this.pageSectionDetail.id;
234+
//let azureMediaPlayer = amp(id);
235+
236+
//if (this.pageSectionDetail.videoAsset.azureMediaAsset) {
237+
// $(`#${id}`).css({ 'height': '', 'border': '1px solid #768692' });
238+
// this.disableVideoControl = false;
239+
//} else {
240+
// this.disableVideoControl = true;
241+
//}
242+
243+
//if (this.pageSectionDetail.videoAsset.thumbnailImageFile) {
244+
// azureMediaPlayer.poster(`/file/download/${this.pageSectionDetail.videoAsset.thumbnailImageFile.filePath}/${this.pageSectionDetail.videoAsset.thumbnailImageFile.fileName}`);
245+
//}
246+
//if (this.pageSectionDetail.videoAsset.azureMediaAsset && this.pageSectionDetail.videoAsset.closedCaptionsFile) {
247+
// azureMediaPlayer.src([{
248+
// type: "application/vnd.ms-sstr+xml",
249+
// src: this.pageSectionDetail.videoAsset.azureMediaAsset.locatorUri,
250+
// protectionInfo: [{ type: 'AES', authenticationToken: `Bearer=${this.pageSectionDetail.videoAsset.azureMediaAsset.authenticationToken}` }]
251+
// }],
252+
// [{ kind: "captions", src: `/file/download/${this.pageSectionDetail.videoAsset.closedCaptionsFile.filePath}/${this.pageSectionDetail.videoAsset.closedCaptionsFile.fileName}`, srclang: "en", label: "english" }]);
253+
//}
254+
//else if (this.pageSectionDetail.videoAsset.azureMediaAsset && !this.pageSectionDetail.videoAsset.closedCaptionsFile) {
255+
// azureMediaPlayer.src([{
256+
// type: "application/vnd.ms-sstr+xml",
257+
// src: this.pageSectionDetail.videoAsset.azureMediaAsset.locatorUri,
258+
// protectionInfo: [{ type: 'AES', authenticationToken: `Bearer=${this.pageSectionDetail.videoAsset.azureMediaAsset.authenticationToken}` }]
259+
// }]);
260+
//}
197261
});
198262
} else {
199263
contentData.getPageSectionDetail(this.section.id).then(x => this.pageSectionDetail = x);
200264
}
201-
},
265+
},
202266
getAESProtection(token: string): string {
203267
var aesProtectionInfo = '{"protectionInfo": [{"type": "AES", "authenticationToken":"Bearer=' + token + '"}], "streamingFormats":["SMOOTH","DASH"]}';
204268
return aesProtectionInfo;
205269
},
206270
getMediaAssetProxyUrl(azureMediaAsset: AzureMediaAssetModel): string {
207-
208271
let playBackUrl = azureMediaAsset.locatorUri;
209272
playBackUrl = playBackUrl.substring(0, playBackUrl.lastIndexOf("manifest")) + "manifest(format=m3u8-aapl)";
210273
211274
let sourceUrl = "/Media/MediaManifest?playBackUrl=" + playBackUrl + "&token=" + azureMediaAsset.authenticationToken;
212275
213276
return sourceUrl;
214277
},
278+
getBearerToken(token: string): string {
279+
return "Bearer=" + token;
280+
},
281+
getMediaPlayUrl(url: string): string {
282+
let sourceUrl = url.substring(0, url.lastIndexOf("manifest")) + "manifest(format=m3u8-cmaf,encryption=cbc)";
283+
return sourceUrl;
284+
},
215285
},
216286
watch: {
217287
section() {
218-
this.load();
288+
this.load();
219289
}
220290
}
221291
})
@@ -225,4 +295,25 @@
225295
pointer-events: none;
226296
opacity: 0.5;
227297
}
298+
299+
.video-container {
300+
height: 0;
301+
width: 100%;
302+
overflow: hidden;
303+
position: relative;
304+
padding-top: 56.25%; /* 16:9 aspect ratio */
305+
background-color: #000;
306+
}
307+
308+
video {
309+
position: absolute;
310+
top: 0;
311+
left: 0;
312+
width: 100%;
313+
height: 100%;
314+
}
315+
316+
video[id^="bitmovinplayer-video"] {
317+
width: 100%;
318+
}
228319
</style>

AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/upload/fileValidation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const file_size_validation = (value: any) => {
88
export const file_extension_validation = (value: any) => {
99
if (!value) { return true; }
1010
let fileExtension = value.name.split(".").pop();
11-
let fileType = ['mp4', 'avi', 'm4v', 'mov', 'mkv', 'mpg', 'mpeg', 'wmv'].find(ext => ext == fileExtension);
11+
let fileType = ['mp4', 'avi', 'm4v', 'mov', 'mkv', 'mpg', 'm2v', 'vob'].find(ext => ext == fileExtension);
1212
return fileType != undefined;
1313
};
1414
export const transcriptfile_extension_validation = (value: any) => {

AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/data/content.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,17 @@ const getAVUnavailableView = async function (): Promise<string> {
273273
throw e;
274274
});
275275
};
276+
const getMKPlayerKey = async function (): Promise<string> {
277+
return await axios.get('/Resource/GetMKPlayerKey')
278+
.then(response => {
279+
return response.data;
280+
})
281+
.catch(e => {
282+
console.error('Error fetching Media Kind MKPlayer Key', e)
283+
throw e;
284+
});
285+
};
286+
276287

277288
export const contentData = {
278289
getUploadSettings,
@@ -295,5 +306,6 @@ export const contentData = {
295306
updateVideoAsset,
296307
getAddAVFlag,
297308
getDisplayAVFlag,
298-
getAVUnavailableView
309+
getAVUnavailableView,
310+
getMKPlayerKey
299311
};

AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur
9595
services.AddScoped<ILogService, LogService>();
9696
services.AddScoped<IRoadmapService, RoadmapService>();
9797
services.AddScoped<IFileService, FileService>();
98-
services.AddScoped<IAzureMediaService, AzureMediaService>();
98+
services.AddScoped<IAzureMediaService, MKIOMediaService>();
9999
services.AddScoped<IResourceSyncService, ResourceSyncService>();
100100
services.AddScoped<ICatalogueService, CatalogueService>();
101101
services.AddScoped<IContentService, ContentService>();

0 commit comments

Comments
 (0)