From 4e7e99231fb9768842288de06768f2596a8507c9 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 12 Mar 2016 17:38:38 -0800 Subject: [PATCH 1/2] chore(typescript): refactor and TS --- audio.android.ts | 333 +----------------------------------- audio.common.ts | 23 --- audio.d.ts | 1 - audio.ios.ts | 13 +- demo/app/app.ts | 4 + demo/app/main-page.ts | 297 ++------------------------------ demo/app/main-view-model.ts | 252 +++++++++++++++++++++++++++ demo/package.json | 36 ++-- src/android/player.ts | 222 ++++++++++++++++++++++++ src/android/recorder.ts | 79 +++++++++ src/common.ts | 0 src/ios/player.ts | 12 ++ src/ios/recorder.ts | 10 ++ tsconfig.json | 1 - 14 files changed, 626 insertions(+), 657 deletions(-) delete mode 100755 audio.common.ts create mode 100644 demo/app/app.ts create mode 100644 demo/app/main-view-model.ts mode change 100755 => 100644 demo/package.json create mode 100644 src/android/player.ts create mode 100644 src/android/recorder.ts create mode 100644 src/common.ts create mode 100644 src/ios/player.ts create mode 100644 src/ios/recorder.ts diff --git a/audio.android.ts b/audio.android.ts index 5c45163..ae43d61 100755 --- a/audio.android.ts +++ b/audio.android.ts @@ -1,324 +1,9 @@ -import {Common} from './audio.common'; -import types = require("utils/types"); -import definition = require("./audio"); -import app = require("application"); -import * as utilsModule from "utils/utils"; -import * as fileSystemModule from "file-system"; -import * as enumsModule from "ui/enums"; - -let MediaPlayer = android.media.MediaPlayer; -let MediaRecorder = android.media.MediaRecorder; - -var utils: typeof utilsModule; -function ensureUtils() { - if (!utils) { - utils = require("utils/utils"); - } -} - -var fs: typeof fileSystemModule; -function ensureFS() { - if (!fs) { - fs = require("file-system"); - } -} - -var enums: typeof enumsModule; -function ensureEnums() { - if (!enums) { - enums = require("ui/enums"); - } -} - -export var playFromFile = function(options: definition.AudioPlayerOptions): Promise { - return new Promise((resolve, reject) => { - try { - var audioPath; - - ensureFS(); - - var fileName = types.isString(options.audioFile) ? options.audioFile.trim() : ""; - if (fileName.indexOf("~/") === 0) { - fileName = fs.path.join(fs.knownFolders.currentApp().path, fileName.replace("~/", "")); - console.log('fileName: ' + fileName); - audioPath = fileName; - } - - var mediaPlayer = new MediaPlayer(); - mediaPlayer.setAudioStreamType(android.media.AudioManager.STREAM_MUSIC); - mediaPlayer.setDataSource(audioPath); - mediaPlayer.prepareAsync(); - - // On Complete - mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener({ - onCompletion: function(mp) { - options.completeCallback(); - } - })); - - // On Error - mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener({ - onError: function(mp: any, what: number, extra: number) { - options.errorCallback(); - } - })); - - // On Info - mediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener({ - onInfo: function(mp: any, what: number, extra: number) { - options.infoCallback(); - } - })) - - // On Prepared - mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener({ - onPrepared: function(mp) { - mp.start(); - resolve(mp); - } - })); - - } catch (ex) { - reject(ex); - } - }); -} - -export var playFromUrl = function(options: definition.AudioPlayerOptions): Promise { - return new Promise((resolve, reject) => { - try { - - var mediaPlayer = new MediaPlayer(); - mediaPlayer.setAudioStreamType(android.media.AudioManager.STREAM_MUSIC); - mediaPlayer.setDataSource(options.audioFile); - mediaPlayer.prepareAsync(); - - // On Complete - if (options.completeCallback) { - mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener({ - onCompletion: function(mp) { - options.completeCallback(); - } - })); - } - - // On Error - if (options.errorCallback) { - mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener({ - onError: function(mp: any, what: number, extra: number) { - options.errorCallback(); - } - })); - } - - // On Info - if (options.infoCallback) { - mediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener({ - onInfo: function(mp: any, what: number, extra: number) { - options.infoCallback(); - } - })) - } - - // On Prepared - mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener({ - onPrepared: function(mp) { - mp.start(); - resolve(mp); - } - })); - - } catch (ex) { - reject(ex); - } - }); -} - -export var pausePlayer = function(player: any): Promise { - return new Promise((resolve, reject) => { - try { - var isPlaying = player.isPlaying(); - if (isPlaying) { - console.log('PAUSE'); - player.pause(); - resolve(true); - } - resolve(false); - } catch (ex) { - reject(ex); - } - }); -} - -export var disposePlayer = function(player: any): Promise { - return new Promise((resolve, reject) => { - try { - player.release(); - resolve(); - } catch (ex) { - reject(ex); - } - }); -} - -export var isAudioPlaying = function(player: any): boolean { - if (player.isPlaying() === true) { - return true; - } else { - return false; - } -} - -export var getAudioTrackDuration = function(player: any): Promise { - return new Promise((resolve, reject) => { - try { - var duration = player.getDuration(); - resolve(duration.toString()); - } catch (ex) { - reject(ex); - } - }); -} - - -/**** AUDIO RECORDING ****/ - -export var canDeviceRecord = function(): boolean { - var pManager = app.android.context.getPackageManager(); - var canRecord = pManager.hasSystemFeature(android.content.pm.PackageManager.FEATURE_MICROPHONE); - if (canRecord) { - return true; - } else { - return false; - } -} - -export var startRecorder = function(options: definition.AudioRecorderOptions): Promise { - return new Promise((resolve, reject) => { - try { - var recorder = new MediaRecorder(); - recorder.setAudioSource(0); - recorder.setOutputFormat(0); - recorder.setAudioEncoder(0); - // recorder.setOutputFile("/sdcard/example.mp4"); - recorder.setOutputFile(options.filename); - recorder.prepare(); - recorder.start(); - - // Is there any benefit to calling start() before setting listener? - - // On Error - recorder.setOnErrorListener(new MediaRecorder.OnErrorListener({ - onError: function(mr: any, what: number, extra: number) { - options.errorCallback({ msg: what, extra: extra }); - } - })); - - // On Info - recorder.setOnInfoListener(new MediaRecorder.OnInfoListener({ - onInfo: function(mr: any, what: number, extra: number) { - options.infoCallback({ msg: what, extra: extra }); - } - })); - - resolve(recorder); - - } catch (ex) { - reject(ex); - } - }); -} - -export var stopRecorder = function(recorder: any): Promise { - return new Promise((resolve, reject) => { - try { - recorder.stop(); - resolve(); - } catch (ex) { - reject(ex); - } - }); -} - -export var disposeRecorder = function(recorder: any): Promise { - return new Promise((resolve, reject) => { - try { - recorder.release(); - resolve(); - } catch (ex) { - reject(ex); - } - }); -} - - - - - - - - - -// export var playFromResource = function(options: definition.AudioPlayerOptions): Promise { -// return new Promise((resolve, reject) => { -// try { -// var audioPath; - -// ensureUtils(); - -// var res = utils.ad.getApplicationContext().getResources(); -// var packageName = utils.ad.getApplication().getPackageName(); -// var identifier = utils.ad.getApplicationContext().getResources().getIdentifier("in_the_night", "raw", packageName); -// console.log(identifier); -// console.log(packageName); -// console.log(res); -// if (res) { -// var resourcePath = "android.resource://" + packageName + "/raw/" + options.audioFile; -// audioPath = resourcePath; -// } - -// var mediaPlayer = new MediaPlayer(); -// mediaPlayer.setAudioStreamType(android.media.AudioManager.STREAM_MUSIC); -// mediaPlayer.setDataSource(audioPath); -// mediaPlayer.prepareAsync(); - -// // On Complete -// if (options.completeCallback) { -// mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener({ -// onCompletion: function(mp) { -// options.completeCallback(); -// } -// })); -// } - -// // On Error -// if (options.errorCallback) { -// mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener({ -// onError: function(mp: any, what: number, extra: number) { -// options.errorCallback({ msg: what, extra: extra }); -// } -// })); -// } - -// // On Info -// if (options.infoCallback) { -// mediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener({ -// onInfo: function(mp: any, what: number, extra: number) { -// options.infoCallback({ msg: what, extra: extra }); -// } -// })) -// } - -// // On Prepared - this resolves and returns the android.media.MediaPlayer; -// mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener({ -// onPrepared: function(mp) { -// mp.start(); -// resolve(mp); -// } -// })); - -// } catch (ex) { -// reject(ex); -// } -// }); -// } \ No newline at end of file +/** + * Player + */ +export * from './src/android/player'; + +/** + * Recorder + */ +export * from './src/android/recorder'; diff --git a/audio.common.ts b/audio.common.ts deleted file mode 100755 index 9495da6..0000000 --- a/audio.common.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as app from 'application'; -import * as dialogs from 'ui/dialogs'; - -export class Common { - public message: string; - - constructor() { - this.message = Utils.SUCCESS_MSG(); - } -} - -export class Utils { - public static SUCCESS_MSG(): string { - let msg = `Your plugin is working on ${app.android ? 'Android' : 'iOS'}.`; - - setTimeout(() => { - dialogs.alert(`${msg} For real. It's really working :)`).then(() => console.log(`Dialog closed.`)); - }, 2000); - - return msg; - } -} - diff --git a/audio.d.ts b/audio.d.ts index 36c529a..03cb2c9 100644 --- a/audio.d.ts +++ b/audio.d.ts @@ -3,7 +3,6 @@ */ declare module "audio" { - import fs = require("file-system"); // export class Audio { // diff --git a/audio.ios.ts b/audio.ios.ts index 5216e9e..2280f5d 100755 --- a/audio.ios.ts +++ b/audio.ios.ts @@ -1,5 +1,10 @@ -import {Common} from './audio.common'; +/** + * Player + */ +export * from './src/ios/player'; + +/** + * Recorder + */ +export * from './src/ios/recorder'; -// export class YourPlugin extends Common { -// -// } \ No newline at end of file diff --git a/demo/app/app.ts b/demo/app/app.ts new file mode 100644 index 0000000..83ec98c --- /dev/null +++ b/demo/app/app.ts @@ -0,0 +1,4 @@ +import * as application from 'application'; +application.mainModule = "main-page"; +application.cssFile = "./app.css"; +application.start(); \ No newline at end of file diff --git a/demo/app/main-page.ts b/demo/app/main-page.ts index d538449..88beb5c 100755 --- a/demo/app/main-page.ts +++ b/demo/app/main-page.ts @@ -1,290 +1,15 @@ -import observable = require('data/observable'); -// import page = require('ui/page'); -import fs = require('file-system'); -import audioModule = require("nativescript-audio"); -import snackbar = require("nativescript-snackbar"); -import app = require("application"); -import color = require("color"); -import platform = require("platform"); -import types = require("utils/types"); +import * as app from 'application'; +import {Color} from 'color'; +import * as platform from 'platform'; +import {AudioDemo} from "./main-view-model"; -var MediaRecorder = android.media.MediaRecorder; -var MediaPlayer = android.media.MediaPlayer; - -var data = new observable.Observable({ - isPlaying: false -}); - - -var recorder; -var mediaPlayer; -var audioSessionId; -var page; - -var audioUrls = [ - { name: 'Fight Club', pic: '~/pics/canoe_girl.jpeg', url: 'http://www.noiseaddicts.com/samples_1w72b820/2514.mp3' }, - { name: 'To The Bat Cave!!!', pic: '~/pics/bears.jpeg', url: 'http://www.noiseaddicts.com/samples_1w72b820/17.mp3' }, - { name: 'Marlon Brando', pic: '~/pics/northern_lights.jpeg', url: 'http://www.noiseaddicts.com/samples_1w72b820/47.mp3' } -]; - -// Event handler for Page "loaded" event attached in main-page.xml function pageLoaded(args) { - // Get the event sender - page = args.object; - page.bindingContext = data; - - if (app.android && platform.device.sdkVersion >= "21") { - var window = app.android.startActivity.getWindow(); - window.setNavigationBarColor(new color.Color("#C2185B").android); - } + var page = args.object; + page.bindingContext = new AudioDemo(); + + if (app.android && platform.device.sdkVersion >= "21") { + var window = app.android.startActivity.getWindow(); + window.setNavigationBarColor(new Color("#C2185B").android); + } } exports.pageLoaded = pageLoaded; - - -function startRecord(args) { - - var canRecord = audioModule.canDeviceRecord(); - - if (canRecord) { - - var audioFolder = fs.knownFolders.currentApp().getFolder("audio"); - console.log(JSON.stringify(audioFolder)); - - var file = "~/audio/recording.mp3"; - - var recorderOptions = { - - filename: audioFolder.path + "/recording.mp3", - - infoCallback: function() { - console.log(); - }, - - errorCallback: function() { - console.log(); - snackbar.simple('Error recording.'); - } - }; - - data.set("isRecording", true); - audioModule.startRecorder(recorderOptions).then(function(result) { - recorder = result; - }, function(err) { - data.set("isRecording", false); - alert(err); - }); - } else { - alert("This device cannot record audio."); - } -} -exports.startRecord = startRecord - -function stopRecord(args) { - audioModule.disposeRecorder(recorder).then(function() { - data.set("isRecording", false); - snackbar.simple("Recorder stopped"); - }, function(ex) { - console.log(ex); - data.set("isRecording", false); - }); -} -exports.stopRecord = stopRecord; - -function getFile(args) { - try { - var audioFolder = fs.knownFolders.currentApp().getFolder("audio"); - var recordedFile = audioFolder.getFile("recording.mp3"); - console.log(JSON.stringify(recordedFile)); - console.log('recording exists: ' + fs.File.exists(recordedFile.path)); - data.set("recordedAudioFile", recordedFile.path); - } catch (ex) { - console.log(ex); - } -} -exports.getFile = getFile; - - -function playRecordedFile(args) { - - var audioFolder = fs.knownFolders.currentApp().getFolder("audio"); - var recordedFile = audioFolder.getFile("recording.mp3"); - console.log("RECORDED FILE : " + JSON.stringify(recordedFile)); - - var playerOptions = { - audioFile: "~/audio/recording.mp3", - - completeCallback: function() { - snackbar.simple("Audio file complete"); - data.set("isPlaying", false); - audioModule.disposePlayer(mediaPlayer).then(function() { - console.log('DISPOSED'); - }, function(err) { - console.log(err); - }); - }, - - errorCallback: function() { - alert('Error callback'); - data.set("isPlaying", false); - }, - - infoCallback: function() { - alert('Info callback'); - } - }; - - data.set("isPlaying", true); - audioModule.playFromFile(playerOptions).then(function(result) { - console.log(result); - mediaPlayer = result; - }, function(err) { - console.log(err); - data.set("isPlaying", false); - }); - -} -exports.playRecordedFile = playRecordedFile; - - - -/***** AUDIO PLAYER *****/ - -function playAudio(filepath, fileType) { - - try { - var playerOptions = { - audioFile: filepath, - - completeCallback: function() { - snackbar.simple("Audio file complete"); - data.set("isPlaying", false); - audioModule.disposePlayer(mediaPlayer).then(function() { - console.log('DISPOSED'); - }, function(err) { - console.log('ERROR disposePlayer: ' + err); - }); - }, - - errorCallback: function(err) { - snackbar.simple('Error occurred during playback.'); - console.log(err); - data.set("isPlaying", false); - }, - - infoCallback: function(info) { - alert('Info callback: ' + info.msg); - console.log("what: " + info); - } - }; - - data.set("isPlaying", true); - - if (fileType === 'localFile') { - audioModule.playFromFile(playerOptions).then(function(result) { - console.log(result); - mediaPlayer = result; - }, function(err) { - console.log(err); - data.set("isPlaying", false); - }); - } else if (fileType === 'remoteFile') { - audioModule.playFromUrl(playerOptions).then(function(result) { - console.log(result); - mediaPlayer = result; - }, function(err) { - console.log(err); - data.set("isPlaying", false); - }); - } - } catch (ex) { - console.log(ex); - } - -} - - - -///** -// * PLAY RESOURCES FILE -// */ -// function playResFile(args) { -// var filepath = 'in_the_night'; -// if (mediaPlayer) { -// mediaPlayer = null; -// } - -// playAudio(filepath, 'resFile'); - -// } -// exports.playResFile = playResFile; - -/** - * PLAY REMOTE AUDIO FILE - */ -function playRemoteFile(args) { - console.log('playRemoteFile'); - var filepath = 'http://www.noiseaddicts.com/samples_1w72b820/2514.mp3'; - if (mediaPlayer) { - mediaPlayer = null; - } - - playAudio(filepath, 'remoteFile'); - -} -exports.playRemoteFile = playRemoteFile; - -/** - * PLAY LOCAL AUDIO FILE from app folder - */ -function playLocalFile(args) { - var filepath = '~/audio/angel.mp3'; - if (mediaPlayer) { - mediaPlayer = null; - } - - playAudio(filepath, 'localFile'); - -} -exports.playLocalFile = playLocalFile; - - - - - - -/** - * PAUSE PLAYING - */ -function pauseAudio(args) { - audioModule.pausePlayer(mediaPlayer).then(function(result) { - console.log(result); - data.set("isPlaying", false); - }, function(err) { - console.log(err); - data.set("isPlaying", true); - }); -} -exports.pauseAudio = pauseAudio; - - - - - -function stopPlaying(args) { - audioModule.disposePlayer(mediaPlayer).then(function() { - snackbar.simple("Media Player Disposed"); - }, function(err) { - console.log(err); - }); -} -exports.stopPlaying = stopPlaying; - - -/** - * RESUME PLAYING - */ -function resumePlaying(args) { - console.log('START'); - mediaPlayer.start(); -} -exports.resumePlaying = resumePlaying; \ No newline at end of file diff --git a/demo/app/main-view-model.ts b/demo/app/main-view-model.ts new file mode 100644 index 0000000..f2bbac1 --- /dev/null +++ b/demo/app/main-view-model.ts @@ -0,0 +1,252 @@ +import {Observable} from 'data/observable'; +import * as fs from 'file-system'; +import * as snackbar from 'nativescript-snackbar'; +import * as app from 'application'; +import * as color from 'color'; +import * as platform from 'platform'; +import {TNSRecorder, TNSPlayer} from 'nativescript-audio'; + +export class AudioDemo extends Observable { + public isPlaying: boolean; + public isRecording: boolean; + public recordedAudioFile: string; + private recorder; + private player; + private audioSessionId; + private page; + private audioUrls: Array = [ + { name: 'Fight Club', pic: '~/pics/canoe_girl.jpeg', url: 'http://www.noiseaddicts.com/samples_1w72b820/2514.mp3' }, + { name: 'To The Bat Cave!!!', pic: '~/pics/bears.jpeg', url: 'http://www.noiseaddicts.com/samples_1w72b820/17.mp3' }, + { name: 'Marlon Brando', pic: '~/pics/northern_lights.jpeg', url: 'http://www.noiseaddicts.com/samples_1w72b820/47.mp3' } + ]; + + constructor() { + super(); + + this.player = new TNSPlayer(); + this.recorder = new TNSRecorder(); + } + + public startRecord(args) { + if (TNSRecorder.CAN_RECORD()) { + + var audioFolder = fs.knownFolders.currentApp().getFolder("audio"); + console.log(JSON.stringify(audioFolder)); + + var file = "~/audio/recording.mp3"; + + var recorderOptions = { + + filename: audioFolder.path + "/recording.mp3", + + infoCallback: () => { + console.log(); + }, + + errorCallback: () => { + console.log(); + snackbar.simple('Error recording.'); + } + }; + + + this.recorder.start(recorderOptions).then((result) => { + this.set("isRecording", true); + }, (err) => { + this.set("isRecording", false); + alert(err); + }); + } else { + alert("This device cannot record audio."); + } + } + + public stopRecord(args) { + this.recorder.dispose().then(() => { + this.set("isRecording", false); + snackbar.simple("Recorder stopped"); + }, (ex) => { + console.log(ex); + this.set("isRecording", false); + }); + } + + public getFile(args) { + try { + var audioFolder = fs.knownFolders.currentApp().getFolder("audio"); + var recordedFile = audioFolder.getFile("recording.mp3"); + console.log(JSON.stringify(recordedFile)); + console.log('recording exists: ' + fs.File.exists(recordedFile.path)); + this.set("recordedAudioFile", recordedFile.path); + } catch (ex) { + console.log(ex); + } + } + + + public playRecordedFile(args) { + + var audioFolder = fs.knownFolders.currentApp().getFolder("audio"); + var recordedFile = audioFolder.getFile("recording.mp3"); + console.log("RECORDED FILE : " + JSON.stringify(recordedFile)); + + var playerOptions = { + audioFile: "~/audio/recording.mp3", + + completeCallback: () => { + snackbar.simple("Audio file complete"); + this.set("isPlaying", false); + this.player.dispose().then(() => { + console.log('DISPOSED'); + }, (err) => { + console.log(err); + }); + }, + + errorCallback: () => { + alert('Error callback'); + this.set("isPlaying", false); + }, + + infoCallback: () => { + alert('Info callback'); + } + }; + + + this.player.playFromFile(playerOptions).then(() => { + this.set("isPlaying", true); + }, (err) => { + console.log(err); + this.set("isPlaying", false); + }); + + } + + + + /***** AUDIO PLAYER *****/ + + public playAudio(filepath: any, fileType: any) { + + try { + var playerOptions = { + audioFile: filepath, + + completeCallback: () => { + snackbar.simple("Audio file complete"); + + this.player.disposePlayer().then(() => { + this.set("isPlaying", false); + console.log('DISPOSED'); + }, (err) => { + console.log('ERROR disposePlayer: ' + err); + }); + }, + + errorCallback: (err) => { + snackbar.simple('Error occurred during playback.'); + console.log(err); + this.set("isPlaying", false); + }, + + infoCallback: (info) => { + alert('Info callback: ' + info.msg); + console.log("what: " + info); + } + }; + + this.set("isPlaying", true); + + if (fileType === 'localFile') { + this.player.playFromFile(playerOptions).then(() => { + this.set("isPlaying", true); + }, (err) => { + console.log(err); + this.set("isPlaying", false); + }); + } else if (fileType === 'remoteFile') { + this.player.playFromUrl(playerOptions).then(() => { + this.set("isPlaying", true); + }, (err) => { + console.log(err); + this.set("isPlaying", false); + }); + } + } catch (ex) { + console.log(ex); + } + + } + + + + ///** + // * PLAY RESOURCES FILE + // */ + // public playResFile(args) { + // var filepath = 'in_the_night'; + + // this.playAudio(filepath, 'resFile'); + + // } + + /** + * PLAY REMOTE AUDIO FILE + */ + public playRemoteFile(args) { + console.log('playRemoteFile'); + var filepath = 'http://www.noiseaddicts.com/samples_1w72b820/2514.mp3'; + + this.playAudio(filepath, 'remoteFile'); + + } + + /** + * PLAY LOCAL AUDIO FILE from app folder + */ + public playLocalFile(args) { + var filepath = '~/audio/angel.mp3'; + + this.playAudio(filepath, 'localFile'); + + } + + + + + + + /** + * PAUSE PLAYING + */ + public pauseAudio(args) { + this.player.pause().then(() => { + this.set("isPlaying", false); + }, (err) => { + console.log(err); + this.set("isPlaying", true); + }); + } + + + + + + public stopPlaying(args) { + this.player.dispose().then(() => { + snackbar.simple("Media Player Disposed"); + }, (err) => { + console.log(err); + }); + } + + + /** + * RESUME PLAYING + */ + public resumePlaying(args) { + console.log('START'); + this.player.start(); + } +} diff --git a/demo/package.json b/demo/package.json old mode 100755 new mode 100644 index 51e1489..21f65a0 --- a/demo/package.json +++ b/demo/package.json @@ -1,19 +1,19 @@ { - "nativescript": { - "id": "org.nativescript.audio", - "tns-ios": { - "version": "1.6.0" - }, - "tns-android": { - "version": "1.6.3" - } - }, - "dependencies": { - "nativescript-audio": "file:..", - "tns-core-modules": "latest" - }, - "devDependencies": { - "nativescript-dev-typescript": "^0.3.0", - "typescript": "^1.8.7" - } -} \ No newline at end of file + "nativescript": { + "id": "org.nativescript.audio", + "tns-ios": { + "version": "1.6.0" + }, + "tns-android": { + "version": "1.6.3" + } + }, + "dependencies": { + "nativescript-audio": "file:..", + "tns-core-modules": "latest" + }, + "devDependencies": { + "nativescript-dev-typescript": "^0.3.0", + "typescript": "^1.8.7" + } +} diff --git a/src/android/player.ts b/src/android/player.ts new file mode 100644 index 0000000..598498c --- /dev/null +++ b/src/android/player.ts @@ -0,0 +1,222 @@ +import {isString} from 'utils/types'; +import {AudioPlayerOptions} from '../../audio'; +import * as app from 'application'; +import * as utils from 'utils/utils'; +import * as fs from 'file-system'; +import * as enums from 'ui/enums'; + +export class TNSPlayer { + private player: any; + + constructor() { + this.player = new android.media.MediaPlayer(); + } + + public playFromFile(options: AudioPlayerOptions): Promise { + return new Promise((resolve, reject) => { + try { + let MediaPlayer = android.media.MediaPlayer; + let audioPath; + + let fileName = isString(options.audioFile) ? options.audioFile.trim() : ""; + if (fileName.indexOf("~/") === 0) { + fileName = fs.path.join(fs.knownFolders.currentApp().path, fileName.replace("~/", "")); + console.log('fileName: ' + fileName); + audioPath = fileName; + } + + this.player.setAudioStreamType(android.media.AudioManager.STREAM_MUSIC); + this.player.setDataSource(audioPath); + this.player.prepareAsync(); + + // On Complete + this.player.setOnCompletionListener(MediaPlayer.OnCompletionListener({ + onCompletion: (mp) => { + options.completeCallback(); + } + })); + + // On Error + this.player.setOnErrorListener(MediaPlayer.OnErrorListener({ + onError: (mp: any, what: number, extra: number) => { + options.errorCallback(); + } + })); + + // On Info + this.player.setOnInfoListener(MediaPlayer.OnInfoListener({ + onInfo: (mp: any, what: number, extra: number) => { + options.infoCallback(); + } + })) + + // On Prepared + this.player.setOnPreparedListener(MediaPlayer.OnPreparedListener({ + onPrepared: (mp) => { + mp.start(); + resolve(); + } + })); + + } catch (ex) { + reject(ex); + } + }); + } + + public playFromUrl(options: AudioPlayerOptions): Promise { + return new Promise((resolve, reject) => { + try { + let MediaPlayer = android.media.MediaPlayer; + + this.player.setAudioStreamType(android.media.AudioManager.STREAM_MUSIC); + this.player.setDataSource(options.audioFile); + this.player.prepareAsync(); + + // On Complete + if (options.completeCallback) { + this.player.setOnCompletionListener(MediaPlayer.OnCompletionListener({ + onCompletion: (mp) => { + options.completeCallback(); + } + })); + } + + // On Error + if (options.errorCallback) { + this.player.setOnErrorListener(MediaPlayer.OnErrorListener({ + onError: (mp: any, what: number, extra: number) => { + options.errorCallback(); + } + })); + } + + // On Info + if (options.infoCallback) { + this.player.setOnInfoListener(MediaPlayer.OnInfoListener({ + onInfo: (mp: any, what: number, extra: number) => { + options.infoCallback(); + } + })) + } + + // On Prepared + this.player.setOnPreparedListener(MediaPlayer.OnPreparedListener({ + onPrepared: (mp) => { + mp.start(); + resolve(); + } + })); + + } catch (ex) { + reject(ex); + } + }); + } + + public pause(): Promise { + return new Promise((resolve, reject) => { + try { + var isPlaying = this.player.isPlaying(); + if (isPlaying) { + console.log('PAUSE'); + this.player.pause(); + resolve(true); + } + resolve(false); + } catch (ex) { + reject(ex); + } + }); + } + + public dispose(): Promise { + return new Promise((resolve, reject) => { + try { + this.player.release(); + resolve(); + } catch (ex) { + reject(ex); + } + }); + } + + public isAudioPlaying(): boolean { + return this.player.isPlaying(); + } + + public getAudioTrackDuration(): Promise { + return new Promise((resolve, reject) => { + try { + var duration = this.player.getDuration(); + resolve(duration.toString()); + } catch (ex) { + reject(ex); + } + }); + } +} + +// TODO: convert this into above + +// export var playFromResource = function(options: definition.AudioPlayerOptions): Promise { +// return new Promise((resolve, reject) => { +// try { +// var audioPath; + +// var res = utils.ad.getApplicationContext().getResources(); +// var packageName = utils.ad.getApplication().getPackageName(); +// var identifier = utils.ad.getApplicationContext().getResources().getIdentifier("in_the_night", "raw", packageName); +// console.log(identifier); +// console.log(packageName); +// console.log(res); +// if (res) { +// var resourcePath = "android.resource://" + packageName + "/raw/" + options.audioFile; +// audioPath = resourcePath; +// } + +// var mediaPlayer = new MediaPlayer(); +// mediaPlayer.setAudioStreamType(android.media.AudioManager.STREAM_MUSIC); +// mediaPlayer.setDataSource(audioPath); +// mediaPlayer.prepareAsync(); + +// // On Complete +// if (options.completeCallback) { +// mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener({ +// onCompletion: function(mp) { +// options.completeCallback(); +// } +// })); +// } + +// // On Error +// if (options.errorCallback) { +// mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener({ +// onError: function(mp: any, what: number, extra: number) { +// options.errorCallback({ msg: what, extra: extra }); +// } +// })); +// } + +// // On Info +// if (options.infoCallback) { +// mediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener({ +// onInfo: function(mp: any, what: number, extra: number) { +// options.infoCallback({ msg: what, extra: extra }); +// } +// })) +// } + +// // On Prepared - this resolves and returns the android.media.MediaPlayer; +// mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener({ +// onPrepared: function(mp) { +// mp.start(); +// resolve(mp); +// } +// })); + +// } catch (ex) { +// reject(ex); +// } +// }); +// } \ No newline at end of file diff --git a/src/android/recorder.ts b/src/android/recorder.ts new file mode 100644 index 0000000..8a72295 --- /dev/null +++ b/src/android/recorder.ts @@ -0,0 +1,79 @@ +import * as app from 'application'; +import * as definition from '../../audio'; + +export class TNSRecorder { + private recorder: any; + + constructor() { + this.recorder = new android.media.MediaRecorder(); + } + + public static CAN_RECORD(): boolean { + var pManager = app.android.context.getPackageManager(); + var canRecord = pManager.hasSystemFeature(android.content.pm.PackageManager.FEATURE_MICROPHONE); + if (canRecord) { + return true; + } else { + return false; + } + } + + public start(options: definition.AudioRecorderOptions): Promise { + return new Promise((resolve, reject) => { + try { + let MediaRecorder = android.media.MediaRecorder; + + this.recorder.setAudioSource(0); + this.recorder.setOutputFormat(0); + this.recorder.setAudioEncoder(0); + // recorder.setOutputFile("/sdcard/example.mp4"); + this.recorder.setOutputFile(options.filename); + this.recorder.prepare(); + this.recorder.start(); + + // Is there any benefit to calling start() before setting listener? + + // On Error + this.recorder.setOnErrorListener(MediaRecorder.OnErrorListener({ + onError: (mr: any, what: number, extra: number) => { + options.errorCallback({ msg: what, extra: extra }); + } + })); + + // On Info + this.recorder.setOnInfoListener(MediaRecorder.OnInfoListener({ + onInfo: (mr: any, what: number, extra: number) => { + options.infoCallback({ msg: what, extra: extra }); + } + })); + + resolve(); + + } catch (ex) { + reject(ex); + } + }); + } + + public stop(): Promise { + return new Promise((resolve, reject) => { + try { + this.recorder.stop(); + resolve(); + } catch (ex) { + reject(ex); + } + }); + } + + public dispose(): Promise { + return new Promise((resolve, reject) => { + try { + this.recorder.release(); + resolve(); + } catch (ex) { + reject(ex); + } + }); + } +} diff --git a/src/common.ts b/src/common.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/ios/player.ts b/src/ios/player.ts new file mode 100644 index 0000000..83ccf04 --- /dev/null +++ b/src/ios/player.ts @@ -0,0 +1,12 @@ +import * as app from 'application'; +import * as definition from '../../audio'; +import * as fs from 'file-system'; +import {AudioPlayerOptions} from '../../audio'; + +export class TNSPlayer { + private recorder: any; + + constructor() { + // this.recorder = ? + } +} \ No newline at end of file diff --git a/src/ios/recorder.ts b/src/ios/recorder.ts new file mode 100644 index 0000000..6b03e1f --- /dev/null +++ b/src/ios/recorder.ts @@ -0,0 +1,10 @@ +import * as app from 'application'; +import * as definition from '../../audio'; + +export class TNSRecorder { + private recorder: any; + + constructor() { + // this.recorder = ? + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 5e3be24..ce9b1e6 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,6 @@ }, "files": [ "demo/node_modules/tns-core-modules/tns-core-modules.d.ts", - "audio.common.ts", "audio.android.ts", "audio.ios.ts" ], From 302164e8d642b0b4592e649f8fd4d750e5c48e8c Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Wed, 16 Mar 2016 10:36:30 -0700 Subject: [PATCH 2/2] feat(iOS): Player using AVAudioPlayer, Recorder using AVAudioRecorder --- LICENSE | 4 +- README.md | 63 +++++++------ audio.android.ts | 5 + audio.d.ts | 117 ----------------------- audio.ios.ts | 5 + demo/app/App_Resources/iOS/Info.plist | 11 +++ demo/app/main-page.xml | 80 +++++++--------- demo/app/main-view-model.ts | 24 +++-- demo/package.json | 8 +- package.json | 8 +- src/android/player.ts | 23 ++++- src/android/recorder.ts | 7 +- src/common.ts | 55 +++++++++++ src/ios/player.ts | 131 ++++++++++++++++++++++++-- src/ios/recorder.ts | 85 ++++++++++++++++- src/options.ts | 46 +++++++++ 16 files changed, 443 insertions(+), 229 deletions(-) delete mode 100644 audio.d.ts create mode 100644 src/options.ts diff --git a/LICENSE b/LICENSE index 9d6ed2d..b20e04b 100755 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ The MIT License (MIT) -nativescript-yourplugin -Copyright (c) 2016, Your Name +nativescript-audio +Copyright (c) 2016, Brad Martin & Nathan Walker Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index e8798a3..3cdec8a 100755 --- a/README.md +++ b/README.md @@ -1,9 +1,17 @@ # NativeScript-Audio -NativeScript plugin to play and record audio files. +NativeScript plugin to play and record audio files for Android and iOS. -*Currently Android only, iOS is in the works.* +Uses the following native classes: -[Android Media Recorder Docs](http://developer.android.com/reference/android/media/MediaRecorder.html) +#### Android + +* [Player](http://developer.android.com/reference/android/media/MediaPlayer.html) +* [Recorder](http://developer.android.com/reference/android/media/MediaRecorder.html) + +#### iOS + +* [Player](https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAudioPlayerClassReference/) +* [Recorder](https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAudioRecorder_ClassReference/) ## Installation `npm install nativescript-audio` @@ -12,41 +20,38 @@ NativeScript plugin to play and record audio files. ![AudioExample](screens/audiosample.gif) - ## API -#### *Recording* - -##### canDeviceRecord() - *Promise* -- retruns: *boolean* - -##### startRecorder( { filename: string, errorCallback?: Function, infoCallback?: Function } ) - *Promise* -- returns: *recorder* (android.media.MediaRecorder) - -##### stopRecorder(recorder: recorder object from startRecorder) - +#### TNSRecorder -##### disposeRecorder(recorder: recorder object from startRecorder) -- *Free up system resources when done with recorder* +Method | Description +-------- | --------- +`TNSRecorder.CAN_RECORD()`: `boolean` | Determine if ready to record. +`start({ filename: string, errorCallback?: Function, infoCallback?: Function })`: `Promise` | Start recording file. +`stop()`: `void` | Stop recording. +`dispose()`: `void` | Free up system resources when done with recorder. +#### TNSPlayer -#### *Playing* +Method | Description +-------- | --------- +`playFromFile( { audioFile: string, completeCallback?: Function, errorCallback?: Function, infoCallback?: Function; } )`: `Promise` | Play from a file. +`playFromUrl( { audioFile: string, completeCallback?: Function, errorCallback?: Function, infoCallback?: Function; } )`: `Promise` | Play from a url. +`pause()`: `void` | Pause playback. +`dispose()`: `void` | Free up resources when done playing audio. +`isAudioPlaying()`: `boolean` | Determine if player is playing. +`getAudioTrackDuration()`: `Promise` | duration of media file assigned to mediaPlayer -##### playFromFile( { audioFile: string, completeCallback?: Function, errorCallback?: Function, infoCallback?: Function; } ) - *Promise* -- returns mediaPlayer (android.media.MediaPlayer) +## Why the TNS prefixed name? -##### playFromUrl( { audioFile: string, completeCallback?: Function, errorCallback?: Function, infoCallback?: Function; } ) - *Promise* -- returns mediaPlayer (android.media.MediaPlayer) +`TNS` stands for **T**elerik **N**ative**S**cript -##### pausePlayer(mediaPlayer) - *Promise* -- return boolean +iOS uses classes prefixed with `NS` (stemming from the [NeXTSTEP](https://en.wikipedia.org/wiki/NeXTSTEP) days of old): +https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/ -##### disposePlayer(mediaPlayer) - -- Free up resources when done playing audio with this instance of your mediaPlayer +To avoid confusion with iOS native classes, `TNS` is used instead. -##### isAudioPlaying(mediaPlayer) - *Promise* -- returns boolean +# License -##### getAudioTrackDuration(mediaPlayer) - *Promise* -- returns string - duration of media file assigned to mediaPlayer +[MIT](/LICENSE) diff --git a/audio.android.ts b/audio.android.ts index ae43d61..c14882a 100755 --- a/audio.android.ts +++ b/audio.android.ts @@ -1,3 +1,8 @@ +/** + * Option interfaces + */ +export * from './src/options'; + /** * Player */ diff --git a/audio.d.ts b/audio.d.ts deleted file mode 100644 index 03cb2c9..0000000 --- a/audio.d.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Contains the Audio class. - */ - -declare module "audio" { - - // export class Audio { - // - // } - /** - * Starts playing audio file from local app files. - */ - export function playFromFile(options: AudioPlayerOptions): Promise; - - /** - * Starts playing audio file from res/raw folder - */ - export function playFromResource(options: AudioPlayerOptions): Promise; - - /** - * Starts playing audio file from url - */ - export function playFromUrl(options: AudioPlayerOptions): Promise; - - /** - * Pauses playing audio file. - * @param player The audio player to pause. - */ - export function pausePlayer(player: any): Promise; - - /** - * Releases resources from the audio player. - * @param player The audio player to reset. - */ - export function disposePlayer(player: any): Promise; - - /** - * Check if the audio is actively playing. - * @param player The audio player to check. - */ - export function isAudioPlaying(player: any): Promise; - - /** - * Get the duration of the audio file playing. - * @param player The audio player to check length of current file. - */ - export function getAudioTrackDuration(player: any): Promise; - - /** - * Gets the capability of the device if it can record audio. - */ - export function deviceCanRecord(): boolean; - - /** - * Starts the native audio recording control. - */ - export function startRecorder(options: AudioRecorderOptions): Promise; - - /** - * Stops the native audio recording control. - */ - export function stopRecorder(recorder: any): Promise; - - /** - * Releases resources from the recorder. - * @param recorder The audio player to reset. - */ - export function disposeRecorder(recorder: any): Promise; - - /** - * Provides options for the audio player. - */ - export interface AudioPlayerOptions { - /** - * Gets or sets the audio file url. - */ - audioFile: string; - - /** - * Gets or sets the callback when the currently playing audio file completes. - */ - completeCallback?: Function; - - /** - * Gets or sets the callback when an error occurs with the audio player. - */ - errorCallback?: Function; - - /** - * Gets or sets the callback to be invoked to communicate some info and/or warning about the media or its playback. - */ - infoCallback?: Function; - } - - export interface AudioRecorderOptions { - /** - * Gets or sets the recorded file name. - */ - filename: string; - - /** - * Gets or set the max duration of the recording session. - */ - maxDuration?: number; - - /** - * Gets or sets the callback when an error occurs with the media recorder. - */ - errorCallback?: Function; - - /** - * Gets or sets the callback to be invoked to communicate some info and/or warning about the media or its playback. - */ - infoCallback?: Function; - } - -} \ No newline at end of file diff --git a/audio.ios.ts b/audio.ios.ts index 2280f5d..20134f2 100755 --- a/audio.ios.ts +++ b/audio.ios.ts @@ -1,3 +1,8 @@ +/** + * Option interfaces + */ +export * from './src/options'; + /** * Player */ diff --git a/demo/app/App_Resources/iOS/Info.plist b/demo/app/App_Resources/iOS/Info.plist index 18f333a..3b3fe2b 100755 --- a/demo/app/App_Resources/iOS/Info.plist +++ b/demo/app/App_Resources/iOS/Info.plist @@ -41,5 +41,16 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UIBackgroundModes + + audio + + NSMicrophoneUsageDescription + The Audio Recorder needs to access your Microphone to record. diff --git a/demo/app/main-page.xml b/demo/app/main-page.xml index a401623..e652c30 100755 --- a/demo/app/main-page.xml +++ b/demo/app/main-page.xml @@ -1,57 +1,47 @@ - + loaded="pageLoaded"> - + - - - - - - - -