Add Widevine Plugin support
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -166,7 +166,7 @@
|
||||
<div class="flex flex-col mt-2">
|
||||
<label>Wanted Resolution</label>
|
||||
<select formControlName="wantedResolution" class="form-select">
|
||||
<option [value]="res.name" *ngFor="let res of loaded.videoTracks">{{res.name}} ({{res.codec}} - {{humanFileSize(res.bandwidth)}})</option>
|
||||
<option [disabled]="!isDecryptable(res)" [value]="res.name" *ngFor="let res of loaded.videoTracks">{{res.name}} ({{res.codec}} - {{humanFileSize(res.bandwidth)}})</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ReactiveFormsModule, FormBuilder, FormGroup, Validators, FormsModule }
|
||||
import { VideoProcessingService } from './video-processing.service';
|
||||
import { MomentModule } from 'ngx-moment';
|
||||
import { OrderModule } from 'ngx-order-pipe';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -25,6 +26,9 @@ export class AppComponent implements OnInit {
|
||||
loaded: any = null;
|
||||
Math: any = Math;
|
||||
|
||||
// Indicateur pour le préchargement depuis l'extension
|
||||
isPreloadedFromExtension: boolean = false;
|
||||
|
||||
initForm() {
|
||||
this.processingForm = this.fb.group({
|
||||
keys: ['', Validators.required],
|
||||
@@ -37,7 +41,9 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
constructor(private fb: FormBuilder,
|
||||
private videoProcessingService: VideoProcessingService) {
|
||||
private videoProcessingService: VideoProcessingService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute) {
|
||||
this.processingForm = this.fb.group({
|
||||
keys: ['', Validators.required],
|
||||
wantedResolution: ['1920x1080', Validators.required]
|
||||
@@ -51,6 +57,117 @@ export class AppComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
this.startPollingJobsStatus();
|
||||
this.sayHello();
|
||||
this.checkForExtensionPreload();
|
||||
const keysControl = this.processingForm.get('keys');
|
||||
|
||||
if (keysControl) {
|
||||
keysControl.valueChanges.subscribe(value => {
|
||||
const keys = this.parseKeys(value);
|
||||
const videoTracks = this.loaded?.videoTracks || [];
|
||||
const decryptableResolutions = videoTracks.filter((res: any) => {
|
||||
if (!res.defaultKID || res.defaultKID.length === 0)
|
||||
return false;
|
||||
return keys.find(k => k.key.toLowerCase() === res.defaultKID.toLowerCase());
|
||||
});
|
||||
if (decryptableResolutions.length > 0) {
|
||||
this.processingForm.get('wantedResolution')?.enable();
|
||||
const currentResolution = this.processingForm.get('wantedResolution')?.value;
|
||||
if (!decryptableResolutions.find((res: any) => res.name === currentResolution)) {
|
||||
this.processingForm.get('wantedResolution')?.setValue(decryptableResolutions[0].name);
|
||||
}
|
||||
} else {
|
||||
this.processingForm.get('wantedResolution')?.disable();
|
||||
this.processingForm.get('wantedResolution')?.setValue(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'application a été ouverte avec des paramètres depuis l'extension
|
||||
*/
|
||||
private checkForExtensionPreload(): void {
|
||||
this.route.queryParams.subscribe(params => {
|
||||
if (params['mpdUrl'] && params['keys'] && params['autoLoad']) {
|
||||
this.preloadFromExtension(params['mpdUrl'], params['keys']);
|
||||
// this.router.navigate([], {
|
||||
// queryParams: {
|
||||
// mpdUrl: null,
|
||||
// keys: null,
|
||||
// autoLoad: null
|
||||
// },
|
||||
// queryParamsHandling: 'merge'
|
||||
// });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Précharge les données depuis l'extension Widevine
|
||||
*/
|
||||
private preloadFromExtension(mpdUrl: string, keys: string): void {
|
||||
console.log('Preloading data from Widevine extension:', { mpdUrl, keys });
|
||||
|
||||
this.isPreloadedFromExtension = true;
|
||||
|
||||
const filename = this.generateFilenameFromMPD(mpdUrl);
|
||||
|
||||
this.loadForm.patchValue({
|
||||
mpdUrl: mpdUrl,
|
||||
mp4Filename: filename
|
||||
});
|
||||
|
||||
this.processingForm.patchValue({
|
||||
keys: keys
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.onSubmitLoad();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un nom de fichier intelligent basé sur l'URL MPD
|
||||
*/
|
||||
private generateFilenameFromMPD(mpdUrl: string): string {
|
||||
try {
|
||||
const url = new URL(mpdUrl);
|
||||
|
||||
// Extraire des informations de l'URL
|
||||
let filename = 'content';
|
||||
|
||||
// Patterns spécifiques pour différents CDNs
|
||||
if (url.hostname.includes('canalplus') || url.hostname.includes('paramount')) {
|
||||
// Exemple: paramplus.com_HDCTF601GLFR
|
||||
const pathMatch = url.pathname.match(/([A-Z0-9_]+)\/\d+\//);
|
||||
if (pathMatch) {
|
||||
filename = pathMatch[1].toLowerCase().replace(/_/g, '-');
|
||||
}
|
||||
} else if (url.pathname.includes('/')) {
|
||||
// Utiliser le dernier segment sans extension
|
||||
const segments = url.pathname.split('/').filter(s => s);
|
||||
const lastSegment = segments[segments.length - 1];
|
||||
filename = lastSegment.replace('.mpd', '').replace(/[^a-zA-Z0-9-_]/g, '-');
|
||||
}
|
||||
|
||||
// Ajouter un timestamp pour éviter les collisions
|
||||
const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||
return `${filename}_${timestamp}`;
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Could not parse MPD URL for filename generation:', error);
|
||||
const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||
return `widevine_content_${timestamp}`;
|
||||
}
|
||||
}
|
||||
|
||||
private clearURLParams(): void {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('mpdUrl');
|
||||
url.searchParams.delete('keys');
|
||||
url.searchParams.delete('autoLoad');
|
||||
|
||||
window.history.replaceState({}, document.title, url.toString());
|
||||
}
|
||||
|
||||
downloadFileFromAPI(filePath: string, filename: string, job: any) {
|
||||
@@ -130,16 +247,31 @@ export class AppComponent implements OnInit {
|
||||
console.log(data);
|
||||
this.videoProcessingService.startProcess(data).subscribe({
|
||||
next: (response) => {
|
||||
//this.jobId = response.jobId;
|
||||
// Nettoyer les paramètres URL après avoir démarré le processus
|
||||
if (this.isPreloadedFromExtension) {
|
||||
this.clearURLParams();
|
||||
this.isPreloadedFromExtension = false;
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error starting process:', error);
|
||||
}
|
||||
});
|
||||
//this.initForm();
|
||||
}
|
||||
}
|
||||
|
||||
getFirstDecryptableResolution() {
|
||||
const decryptableResolutions = this.loaded.videoTracks.filter((res: any) => this.isDecryptable(res));
|
||||
return decryptableResolutions.length > 0 ? decryptableResolutions[0].name : null;
|
||||
}
|
||||
|
||||
isDecryptable(res: any) {
|
||||
if (!res.defaultKID || res.defaultKID.length === 0)
|
||||
return false;
|
||||
const keys = this.parseKeys(this.processingForm.value.keys);
|
||||
return keys.find(k => k.key.toLowerCase() === res.defaultKID.toLowerCase());
|
||||
}
|
||||
|
||||
displayJobAudio(tracks: any) {
|
||||
if (tracks.length === 0)
|
||||
return 'None';
|
||||
@@ -153,9 +285,22 @@ export class AppComponent implements OnInit {
|
||||
|
||||
this.videoProcessingService.load(formData).subscribe({
|
||||
next: (response) => {
|
||||
console.log(response);
|
||||
this.loaded = response;
|
||||
//this.jobId = response.jobId;
|
||||
|
||||
if (this.isPreloadedFromExtension) {
|
||||
console.log('MPD loaded successfully from Widevine extension');
|
||||
const firstDecryptable = this.getFirstDecryptableResolution();
|
||||
if (firstDecryptable) {
|
||||
this.processingForm.patchValue({
|
||||
wantedResolution: firstDecryptable
|
||||
});
|
||||
this.processingForm.get('wantedResolution')?.enable();
|
||||
}
|
||||
} else {
|
||||
console.log('MPD loaded successfully', response);
|
||||
// disable formData wantedResolution
|
||||
this.processingForm.get('wantedResolution')?.disable();
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error starting process:', error);
|
||||
@@ -165,7 +310,6 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
parseKeys(keysString: string): { key: string, value: string }[] {
|
||||
console.log('KS =', keysString);
|
||||
return keysString.split('\n').map(line => line.trim())
|
||||
.filter(line => line.includes(':'))
|
||||
.map(line => {
|
||||
@@ -180,14 +324,10 @@ export class AppComponent implements OnInit {
|
||||
next: (response) => {
|
||||
this.lastJobSuccess = true;
|
||||
this.jobs = response;
|
||||
// if (['completed', 'failed'].includes(response.state)) {
|
||||
// clearInterval(interval);
|
||||
// }
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error fetching job status:', error);
|
||||
this.lastJobSuccess = false;
|
||||
//clearInterval(interval);
|
||||
}
|
||||
});
|
||||
}, 1500);
|
||||
|
||||
Reference in New Issue
Block a user