Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1f0c473
feat: add transcode plugin
williamjuan027 Aug 10, 2023
52673d3
feat: complete porting objc to ts (WIP)
williamjuan027 Aug 16, 2023
ca3779f
feat: cleanup ios implementation (WIP)
williamjuan027 Aug 23, 2023
b655923
feat: single video transcoding completed (WIP)
williamjuan027 Aug 30, 2023
6bad066
feat: add configurable video options
williamjuan027 Sep 7, 2023
e9b8d30
feat: iOS transcoding with configurable resolution and frame rate
williamjuan027 Sep 13, 2023
6ee2d71
feat: android basic transcoding
williamjuan027 Sep 20, 2023
05b4a3e
feat: add video transcoding for android without audio
williamjuan027 Sep 28, 2023
3dcc612
cleanup
williamjuan027 Sep 28, 2023
b2fbcad
cleanup
williamjuan027 Sep 28, 2023
a77f9de
Merge branch 'feat/transcode' of github.com:VoiceThread/nativescript-…
drangelod Sep 28, 2023
ab29cfa
feat: support multiple aspect ratio and resolution for android
williamjuan027 Oct 4, 2023
0d15b36
add utilities and misc cleanups
williamjuan027 Oct 11, 2023
009182c
feat: remove audio channel check to support more than 2 audio channel…
williamjuan027 Oct 25, 2023
b574fbb
bug: update dispatch_queue type
williamjuan027 Jan 18, 2024
3832be4
demo: adding permissions, using device files picker as main source, g…
drangelod Jan 18, 2024
562a6b5
demo: hide advanced transcoder demo until its ready
williamjuan027 Jan 25, 2024
c60946f
demo: add force flag to transcoder basic demo
williamjuan027 Jan 25, 2024
78f41ab
feat: add resolution check utility
williamjuan027 Jan 25, 2024
f782ff9
feat: add resolution check and allow passing a force flag to bypass t…
williamjuan027 Jan 25, 2024
76c1265
resolve merge conflicts
williamjuan027 Feb 22, 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- [@voicethread/nativescript-custom-rotors](packages/nativescript-custom-rotors/README.md)
- [@voicethread/nativescript-downloader](packages/nativescript-downloader/README.md)
- [@voicethread/nativescript-filepicker](packages/nativescript-filepicker/README.md)
- [@voicethread/nativescript-transcoder](packages/nativescript-transcoder/README.md)

# Setup

Expand Down
3 changes: 2 additions & 1 deletion apps/demo-angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
"@voicethread/nativescript-downloader": "file:../../dist/packages/nativescript-downloader",
"@voicethread/nativescript-audio-player": "file:../../dist/packages/nativescript-audio-player",
"@voicethread/nativescript-audio-recorder": "file:../../dist/packages/nativescript-audio-recorder",
"@voicethread/nativescript-transcoder": "file:../../dist/packages/nativescript-transcoder",
"@voicethread/nativescript-camera": "file:../../dist/packages/nativescript-camera"
},
"devDependencies": {
"@nativescript/android": "~8.4.0",
"@nativescript/ios": "~8.4.0"
}
}
}
1 change: 1 addition & 0 deletions apps/demo-angular/src/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const routes: Routes = [
{ path: 'nativescript-custom-rotors', loadChildren: () => import('./plugin-demos/nativescript-custom-rotors.module').then(m => m.NativescriptCustomRotorsModule) },
{ path: 'nativescript-downloader', loadChildren: () => import('./plugin-demos/nativescript-downloader.module').then(m => m.NativescriptDownloaderModule) },
{ path: 'nativescript-filepicker', loadChildren: () => import('./plugin-demos/nativescript-filepicker.module').then(m => m.NativescriptFilepickerModule) },
{ path: 'nativescript-transcoder', loadChildren: () => import('./plugin-demos/nativescript-transcoder.module').then(m => m.NativescriptTranscoderModule) },
];

@NgModule({
Expand Down
3 changes: 3 additions & 0 deletions apps/demo-angular/src/home.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@ export class HomeComponent {
{
name: 'nativescript-filepicker',
},
{
name: 'nativescript-transcoder',
},
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<ActionBar title="nativescript-transcoder" class="action-bar"> </ActionBar>
<StackLayout class="p-20">
<ScrollView class="h-full">
<StackLayout>
<Button text="Test nativescript-transcoder" (tap)="demoShared.testIt()" class="btn btn-primary"></Button>
</StackLayout>
</ScrollView>
</StackLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Component, NgZone } from '@angular/core';
import { DemoSharedNativescriptTranscoder } from '@demo/shared';
import {} from '@voicethread/nativescript-transcoder';

@Component({
selector: 'demo-nativescript-transcoder',
templateUrl: 'nativescript-transcoder.component.html',
})
export class NativescriptTranscoderComponent {
demoShared: DemoSharedNativescriptTranscoder;

constructor(private _ngZone: NgZone) {}

ngOnInit() {
this.demoShared = new DemoSharedNativescriptTranscoder();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { NativeScriptCommonModule, NativeScriptRouterModule } from '@nativescript/angular';
import { NativescriptTranscoderComponent } from './nativescript-transcoder.component';

@NgModule({
imports: [NativeScriptCommonModule, NativeScriptRouterModule.forChild([{ path: '', component: NativescriptTranscoderComponent }])],
declarations: [NativescriptTranscoderComponent],
schemas: [NO_ERRORS_SCHEMA],
})
export class NativescriptTranscoderModule {}
7 changes: 4 additions & 3 deletions apps/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@
"@nativescript/core": "file:../../node_modules/@nativescript/core",
"@nstudio/nativescript-loading-indicator": "^4.2.0",
"@valor/nativescript-feedback": "^2.0.2",
"@voicethread/nativescript-audio-player": "file:../../packages/nativescript-audio-player",
"@voicethread/nativescript-audio-recorder": "file:../../packages/nativescript-audio-recorder",
"@voicethread/nativescript-custom-rotors": "file:../../packages/nativescript-custom-rotors",
"@voicethread/nativescript-downloader": "file:../../packages/nativescript-downloader",
"@voicethread/nativescript-filepicker": "file:../../packages/nativescript-filepicker",
"@voicethread/nativescript-audio-player": "file:../../packages/nativescript-audio-player",
"@voicethread/nativescript-audio-recorder": "file:../../packages/nativescript-audio-recorder",
"@voicethread/nativescript-transcoder": "file:../../packages/nativescript-transcoder",
"@voicethread/nativescript-camera": "file:../../packages/nativescript-camera",
"nativescript-videoplayer": "^5.0.1"
},
"devDependencies": {
"@nativescript/android": "8.6.2",
"@nativescript/ios": "8.6.3"
}
}
}
9 changes: 8 additions & 1 deletion apps/demo/src/main-page.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd"

<Button text="nativescript-transcoder" tap="{{ viewDemo }}" class="btn btn-primary view-demo"/><Page xmlns="http://schemas.nativescript.org/tns.xsd"
navigatingTo="navigatingTo">
<Page.actionBar>
<ActionBar title="VoiceThread Plugin Demos"
Expand All @@ -17,6 +18,12 @@
tap="{{ viewAudioPlayer }}" />
<Button text="⏺️ Audio Recorder"
tap="{{ viewAudioRecorder }}" />
<Button text="🔭 Basic Transcoder"
tap="{{ viewBasicTranscoder }}" />

<!-- Disabling for now until it is feature complete -->
<!-- <Button text="🚀 Advanced Transcoder"
tap="{{ viewTranscoder }}" /> -->
<Button text="📷 Camera"
tap="{{ viewCamera }}" />
</StackLayout>
Expand Down
13 changes: 13 additions & 0 deletions apps/demo/src/main-view-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ export class MainViewModel extends Observable {
moduleName: 'plugin-demos/nativescript-audio-recorder',
});
}

viewBasicTranscoder() {
Frame.topmost().navigate({
moduleName: 'plugin-demos/nativescript-transcoder-basic',
});
}

viewTranscoder() {
Frame.topmost().navigate({
moduleName: 'plugin-demos/nativescript-transcoder',
});
}

async viewCamera() {
let permsok = true;
//check for permissions first before routing
Expand Down
189 changes: 189 additions & 0 deletions apps/demo/src/plugin-demos/nativescript-transcoder-basic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { EventData, Page, File, Frame, knownFolders, Button, Label, Color, isAndroid, Device, Progress, isIOS } from '@nativescript/core';
import { DemoSharedNativescriptTranscoder } from '@demo/shared';
import { filePicker, galleryPicker, MediaType } from '@voicethread/nativescript-filepicker';
import { MessageData, NativescriptTranscoder } from '@voicethread/nativescript-transcoder';
import { check as checkPermission, request, request as requestPermission, checkMultiple } from '@nativescript-community/perms';
import { Video } from 'nativescript-videoplayer';
import { executeOnMainThread } from '@nativescript/core/utils';

export function navigatingTo(args: EventData) {
const page = <Page>args.object;
page.bindingContext = new DemoModel();
if (isIOS) {
(page.getViewById('ios-gallery-button') as Button).visibility = 'visible';
}
}

export class DemoModel extends DemoSharedNativescriptTranscoder {
pickedFile: File | undefined = undefined;
transcoder: NativescriptTranscoder;
count = 0;

constructor() {
super();
this.transcoder = new NativescriptTranscoder();
//Note: enabling logging may slow down encoding due to frequent event handling and console output
this.transcoder.setLogLevel('verbose');
}

//Pick video from device files
async pickVideo() {
this.pickedFile = undefined;
let canPick = true;
if (isAndroid && +Device.sdkVersion > 32) {
const result = await checkMultiple({ photo: {}, audio: {}, video: {} });
if (result['photo'] != 'authorized') {
console.log('No photo permission, requesting...');
await request('photo').then(result => {
console.log('Request result', result);
if (result[0] != 'authorized') canPick = false;
});
}
if (result['video'] != 'authorized') {
console.log('No video permission, requesting...');
await request('video').then(result => {
console.log('Request result', result);
if (result[0] != 'authorized') canPick = false;
});
}
if (result['audio'] != 'authorized') {
console.log('No audio permission, requesting...');
await request('audio').then(result => {
console.log('Request result', result);
if (result[0] != 'authorized') canPick = false;
});
}
console.log('canPick?:', canPick);
} else if (isAndroid) {
//just request external_storage perms otherwise
const result = await checkPermission('storage');
if (result['storage'] != 'authorized') console.log('No storage permission, requesting...');
await request('storage').then(result => {
console.log('Request result', result);
if (result['android.permission.READ_EXTERNAL_STORAGE'] != 'authorized') canPick = false;
});
}

if (canPick) {
let files = await filePicker(MediaType.VIDEO, false);
console.log('files', files);
if (files.length) this.pickedFile = files[0];

console.log('Selected file', this?.pickedFile, this?.pickedFile?.path);
} else alert('Need permissions to pick files from device storage');
}

//Pick video from iOS photos gallery
async pickVideoGallery() {
this.pickedFile = undefined;
checkPermission('photo').then(async permres => {
if (permres[0] == 'undetermined' || permres[0] == 'authorized') {
await requestPermission('photo').then(async result => {
if (result[0] == 'authorized') {
try {
const files = await galleryPicker(MediaType.VIDEO, false);
if (files.length) this.pickedFile = files?.[0];
console.log('Selected file', this?.pickedFile, this?.pickedFile?.path);
} catch (err) {
if (err) alert(err?.message);
}
} else alert("No permission for files, can't open picker");
});
} else alert("No permission for files, can't open picker. Grant this permission in app settings first and then try again");
});
}

processVideo480() {
this.processVideo('480p');
}

processVideo480FR5() {
this.processVideo('480p', 5);
}

processVideo720() {
this.processVideo('720p');
}

processVideo1080() {
this.processVideo('1080p');
}

processVideo(quality: '480p' | '720p' | '1080p', frameRate?: number) {
if (!this.pickedFile) {
console.error('No file selected to process');
return;
}
// android doesn't support 480p
if (isAndroid && quality === '480p') {
console.error('Android does not support 480p!');
return;
}
const tempPath = knownFolders.documents().getFile(`video-copy-${this.count}.mp4`).path;
this.count += 1;
if (File.exists(tempPath)) {
const file = File.fromPath(tempPath);
file.removeSync();
}
console.log('[PROCESSING STARTED] quality: ' + quality + (frameRate ? ' frameRate:' + frameRate : ''));
const video = Frame.topmost().currentPage.getViewById('nativeVideoPlayer') as Video;
video.visibility = 'collapsed';
const outputDetailsLabel: Label = Frame.topmost().getViewById('outputDetails');
outputDetailsLabel.visibility = 'collapsed';
const progressBar = Frame.topmost().currentPage.getViewById('transcodingProgress') as Progress;
progressBar.value = 0;
this.transcoder.on(NativescriptTranscoder.TRANSCODING_PROGRESS, (payload: MessageData) => {
executeOnMainThread(() => {
progressBar.value = payload.data.progress * 100;
});
});
const timeStarted = new Date().getTime();
this.transcoder
.transcode(
this.pickedFile.path,
tempPath,
isAndroid
? {
quality: quality,
// force: true, // set to true if you want to force transocding to the same or higher resolution
}
: {
quality: quality,
frameRate: frameRate || 30,
// force: true, // set to true if you want to force transocding to the same or higher resolution
}
)
.then(transcodedFile => {
const timeTaken = (new Date().getTime() - timeStarted) / 1000;
progressBar.value = 100;
console.log('[PROCCESSING COMPLETED]');
console.log('[Original Size]', this.transcoder.getVideoSizeString(this.pickedFile.path));
const originalResolution = this.transcoder.getVideoResolution(this.pickedFile.path);
console.log('[Original Resolution]', `${originalResolution.width}x${originalResolution.height}`);
console.log('[Transcoded Size]', this.transcoder.getVideoSizeString(transcodedFile.path));
console.log('[Percentage Reduced]', `${(((this.pickedFile.size - transcodedFile.size) / this.pickedFile.size) * 100).toFixed(2)}%`);
const resolution = this.transcoder.getVideoResolution(tempPath);
console.log('[Transcoded Resolution]', `${resolution.width}x${resolution.height}`);
console.log('[Time Taken]', `${timeTaken} seconds`);
video.visibility = 'visible';
video.opacity = 1;
video.src = tempPath;
video.loop = true;
video.controls = true;
video.play();
outputDetailsLabel.visibility = 'visible';
outputDetailsLabel.text = `Output Size: ${this.transcoder.getVideoSizeString(transcodedFile.path)}`;
outputDetailsLabel.textWrap = true;
outputDetailsLabel.fontSize = 16;
outputDetailsLabel.color = new Color('#ffffff');
})
.catch(error => {
console.error('[Error Transcoding]', error);
outputDetailsLabel.visibility = 'visible';
outputDetailsLabel.text = `Error: ${error}`;
outputDetailsLabel.textWrap = true;
outputDetailsLabel.fontSize = 16;
outputDetailsLabel.color = new Color('#C70300');
});
}
}
40 changes: 40 additions & 0 deletions apps/demo/src/plugin-demos/nativescript-transcoder-basic.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:VideoPlayer="nativescript-videoplayer"
navigatingTo="navigatingTo">
<Page.actionBar>
<ActionBar title="Video Processing Basic"
icon="">
</ActionBar>
</Page.actionBar>
<StackLayout>
<!-- asset gathering -->
<ScrollView class="h-full">
<StackLayout class="p-20">
<Button text="Pick Video (Files)"
tap="{{ pickVideo }}"/>
<Button text="Pick Video (Gallery)"
id="ios-gallery-button"
visibility="collapse"
tap="{{ pickVideoGallery }}"/>
<Button text="480p (iOS only)"
tap="{{ processVideo480 }}"/>
<Button text="480p [FR 5] (iOS only)"
tap="{{ processVideo480FR5 }}"/>
<Button text="720p"
tap="{{ processVideo720 }}"/>
<Button text="1080p"
tap="{{ processVideo1080 }}"/>
<Progress id="transcodingProgress"
maxValue="100"/>
<VideoPlayer:Video id="nativeVideoPlayer"
controls="true"
loop="true"
autoplay="false"
height="280"
opacity="0" />
<Label id="outputDetails"
marginTop="20" />
</StackLayout>
</ScrollView>
</StackLayout>
</Page>
Loading