Skip to content

Commit

Permalink
Share with the world 💥
Browse files Browse the repository at this point in the history
  • Loading branch information
rossmartin committed Mar 9, 2016
0 parents commit d1b28e8
Show file tree
Hide file tree
Showing 78 changed files with 1,407 additions and 0 deletions.
29 changes: 29 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Specifies intentionally untracked files to ignore when using Git
# http:https://git-scm.com/docs/gitignore

*~
*.sw[mnpcod]
*.log
*.tmp
*.tmp.*
log.txt
*.sublime-project
*.sublime-workspace

.idea/
.sass-cache/
.versions/
coverage/
dist/
node_modules/
tmp/
temp/
www/build/
$RECYCLE.BIN/

.DS_Store
Thumbs.db
UserInterfaceState.xcuserstate

platforms/
plugins/
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## VideoEditor: An Ionic 2 App :camera: :video_camera: :scissors:

This is an Ionic 2 app that shows how to use the great [cordova-plugin-video-editor](https://github.com/jbavari/cordova-plugin-video-editor) and [cordova-plugin-instagram-assets-picker](https://github.com/rossmartin/cordova-plugin-instagram-assets-picker) plugins. It allows transcoding a video with every possible setting the video editor plugin provides as well as trimming, creating thumbnails, and getting video info (width, height orientation, duration, size, & bitrate).

![GIF](example.gif)

Watch the video [here](https://youtu.be/WddM6xNlOuA).

1. Install the the latest beta version of the Ionic CLI:
```
npm install -g ionic@beta
```

1. Clone this repository
```
git clone https://github.com/rossmartin/video-editor-ionic2
```

1. Navigate to the app directory:
```
cd video-editor-ionic2
```

1. Install the dependencies
```
npm install
```

From here you can build and run the app on different platforms using the traditional Ionic commands (`ionic build ios`, etc.)

I have only tested this app on iOS but it should run fine on Android. Transcoding videos on Android will be much slower because the video editor plugin uses ffmpeg on Android. I have had terrible luck with HTML5 videos in the past on Android as well so YMMV there also.
26 changes: 26 additions & 0 deletions app/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {App, Platform} from 'ionic/ionic';
import {TabsPage} from './pages/tabs/tabs';


@App({
template: '<ion-nav [root]="rootPage"></ion-nav>',
config: {} // http:https://ionicframework.com/docs/v2/api/config/Config/
})
export class MyApp {
constructor(platform: Platform) {
this.rootPage = TabsPage;

platform.ready().then(() => {
if (window.cordova) {
if (window.cordova.plugins && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
cordova.plugins.Keyboard.disableScroll(true);
}

if (window.StatusBar) {
StatusBar.styleDefault();
}
}
});
}
}
51 changes: 51 additions & 0 deletions app/pages/edit-video/edit-video.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<ion-navbar *navbar>
<ion-title>Edit Video</ion-title>

<ion-buttons end>
<button (click)="performEdit()">Next</button>
</ion-buttons>
</ion-navbar>

<ion-content padding class="edit-video">

<ion-segment [(ngModel)]="editAction">
<ion-segment-button value="trim" (click)="onTrimButtonClick($event)">
<ion-icon name="cut"></ion-icon>
Trim
</ion-segment-button>
<ion-segment-button value="cover" (click)="onCoverFrameButtonClick($event)">
<ion-icon name="image"></ion-icon>
Cover Frame
</ion-segment-button>
</ion-segment>


<div (click)="onVideoClick($event)" style="margin-top: 5px;">
<video id="edit-video-element"
width="100%"
webkit-playsinline
poster="{{thumbnailPath}}"
src="{{videoPath}}">
</video>
</div>

<!-- can't use ngSwitch here -->

<div [class.hide]="editAction !== 'trim'">
<div style="margin: 2px 15px 9px 15px;">
<div id="trim-slider"></div>
</div>

<div *ngIf="videoDuration">
Duration: {{videoDuration}} seconds
</div>
</div>

<div [class.hide]="editAction !== 'cover'">
<div style="margin: 2px 15px 9px 15px;">
<div id="cover-frame-slider"></div>
</div>
</div>


</ion-content>
237 changes: 237 additions & 0 deletions app/pages/edit-video/edit-video.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import {Page, NavController, NavParams} from 'ionic/ionic';
import {NgZone} from 'angular2/core';
import {VideoResultPage} from '../video-result/video-result';


@Page({
templateUrl: 'build/pages/edit-video/edit-video.html'
})
export class EditVideoPage {
constructor(nav:NavController, navParams:NavParams) {
this.zone = new NgZone({ enableLongStackTrace: false });
this.nav = nav;
this.videoPath = navParams.get('videoPath');
this.thumbnailPath = navParams.get('thumbnailPath');
}

onPageWillEnter() {
this.editAction = this.lastEditAction || 'trim';
}

onPageDidEnter() {
setTimeout(() => {
let video = this.video = document.querySelector('#edit-video-element');
video.play();
video.pause();
video.currentTime = this.lastTrimTime || 0;

// declare shared trim logic vars here
// this is done to safely reference them in setupCoverFrameLogic
// this ensures the cover frame can only be set within the trimmed range
this.videoTrimStart = 0;
this.videoTrimEnd = video.duration;
this.videoDuration = (this.videoTrimEnd - this.videoTrimStart).toFixed(2);

this.setupTrimLogic();
this.setupCoverFrameLogic();
}, 500);
}

setupTrimLogic() {
let video = this.video;
let trimSlider = this.trimSlider = document.querySelector('#trim-slider');
let numTrimSliderUpdates = 0;

if (typeof trimSlider.noUiSlider !== 'undefined') {
console.log('trimSlider already instantiated');
// no need to recreate the slider it is already in the DOM
// this can happen if coming back here from settings
return;
}

noUiSlider.create(trimSlider, {
start: [0, video.duration],
limit: video.duration,
tooltips: [true, true],
connect: true,
range: {
min: 0,
max: video.duration
}
});

trimSlider.noUiSlider.on('update', (values, handle) => {
// update is called twice before anything actually happens, this fixes it
if (numTrimSliderUpdates < 3) {
numTrimSliderUpdates++;
return;
}

let value = Number(values[handle]);

(handle) ? this.videoTrimEnd = value : this.videoTrimStart = value;

this.videoDuration = (this.videoTrimEnd - this.videoTrimStart).toFixed(2);

video.currentTime = this.lastTrimTime = value;
});
}

setupCoverFrameLogic() {
let video = this.video;
let coverFrameSlider = this.coverFrameSlider = document.querySelector('#cover-frame-slider');

if (typeof coverFrameSlider.noUiSlider !== 'undefined') {
console.log('coverFrameSlider already instantiated');
// no need to recreate the slider it is already in the DOM
// this can happen if coming back here from settings
return;
}

this.coverFrameTime = this.videoTrimStart;

noUiSlider.create(coverFrameSlider, {
start: this.videoTrimStart,
connect: 'lower',
tooltips: true,
range: {
min: this.videoTrimStart,
max: this.videoTrimEnd
}
});

coverFrameSlider.noUiSlider.on('update', (values, handle) => {
let value = Number(values[handle]);
video.currentTime = this.coverFrameTime = value;
});
}

onTrimButtonClick(e) {
this.editAction = this.lastEditAction = 'trim';
this.video.currentTime = this.lastTrimTime || 0;
}

onCoverFrameButtonClick(e) {
this.editAction = this.lastEditAction = 'cover';

if (this.coverFrameTime < this.videoTrimStart) {
this.coverFrameTime = this.videoTrimStart;
}

if (this.coverFrameTime > this.videoTrimEnd) {
this.coverFrameTime = this.videoTrimEnd;
}

this.video.currentTime = this.coverFrameTime || 0;

this.coverFrameSlider.noUiSlider.updateOptions({
range: {
min: this.videoTrimStart,
max: this.videoTrimEnd
}
});
}

onVideoClick(e) {
(this.video.paused) ? this.video.play() : this.video.pause();
}

performEdit() {
window.plugins.spinnerDialog.show(null, null, true);

let ls = window.localStorage;
let options = {
fileUri: this.videoPath,
outputFileName: new Date().getTime(),
atTime: this.coverFrameTime,
thumbnailQuality: ls['thumbnailQuality'] || 100,
thumbnailMaintainAspectRatio: ls['thumbnailMaintainAspectRatio'] || true
};
let widthOption = ls['thumbnailWidth'];
let heightOption = ls['thumbnailHeight'];

if (widthOption && widthOption !== 0) {
options.width = widthOption;
}

if (heightOption && heightOption !== 0) {
options.height = heightOption;
}

// not sure how to use promises/observables yet in angular 2
// create the thumbnail, trim the video, then transcode it

VideoEditor.createThumbnail(
(result) => {
console.log('createThumbnail success, result: ', result);
this.newThumbnailPath = result;
this.trimVideo();
},
(err) => {
console.log('createThumbnail error, err: ', err);
},
options
);

}

trimVideo() {
VideoEditor.trim(
(result) => {
console.log('trim success, result: ', result);
this.transcodeVideo(result);
},
(err) => {
console.log('trim error, err: ', err);
},
{
fileUri: this.videoPath,
outputFileName: new Date().getTime(),
trimStart: this.videoTrimStart,
trimEnd: this.videoTrimEnd
}
);
}

transcodeVideo(trimmedVideoPath) {
let ls = window.localStorage;
let widthOption = ls['width'];
let heightOption = ls['height'];
let options = {
fileUri: trimmedVideoPath,
outputFileName: new Date().getTime(),
outputFileType: VideoEditorOptions.OutputFileType.MPEG4,
videoBitrate: ls['videoBitrate'] || 1000000, // 1 megabit
audioChannels: ls['audioChannels'] || 2,
audioSampleRate: ls['audioSampleRate'] || 44100,
audioBitrate: ls['audioBitrate'] || 128000,
maintainAspectRatio: ls['maintainAspectRatio'] || true,
optimizeForNetworkUse: ls['optimizeForNetworkUse'] || true,
saveToLibrary: ls['saveToLibrary'] || true
};

if (widthOption && widthOption !== 0) {
options.width = widthOption;
}

if (heightOption && heightOption !== 0) {
options.height = heightOption;
}

VideoEditor.transcodeVideo(
(result) => {
console.log('transcodeVideo success, result: ', result);
window.plugins.spinnerDialog.hide();
this.nav.push(VideoResultPage, {
videoPath: result,
thumbnailPath: this.newThumbnailPath
});
},
(err) => {
console.log('transcodeVideo error, err: ', err);
},
options
);
}

}
17 changes: 17 additions & 0 deletions app/pages/edit-video/edit-video.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.noUi-tooltip {
opacity: 0.8
}

.noUi-horizontal .noUi-handle-upper .noUi-tooltip {
bottom: 28px !important;
top: -32px !important;
}

.noUi-connect {
background: map-get($colors, primary) !important;
}

.edit-video ion-segment-button ion-icon {
position: relative;
top: 2px;
}
Loading

0 comments on commit d1b28e8

Please sign in to comment.