blob: f7101ee8a72b890fba98e6abc62d6649d1a11a6b [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/* global Windows */
var Media = require('cordova-plugin-media.Media');
var MediaError = require('cordova-plugin-media.MediaError');
var recordedFile;
var tempFolderAppDataBasePath = 'ms-appdata:///temp/';
var localFolderAppDataBasePath = 'ms-appdata:///local/';
var tempFolderFullPath = Windows.Storage.ApplicationData.current.temporaryFolder.path;
var localFolderFullPath = Windows.Storage.ApplicationData.current.localFolder.path;
var PARAMETER_IS_INCORRECT = -2147024809;
var SUPPORTED_EXTENSIONS = ['.mp3', '.wma', '.wav', '.cda', '.adx', '.wm', '.m3u', '.wmx', '.m4a'];
var SUPPORTED_PREFIXES = ['http', 'https', 'rstp'];
var fsTypes = {
PERSISTENT: 'PERSISTENT',
TEMPORARY: 'TEMPORARY'
};
module.exports = {
mediaCaptureMrg: null,
// Initiates the audio file
create: function (win, lose, args) {
var id = args[0];
var srcUri = processUri(args[1]);
var createAudioNode = !!args[2];
var thisM = Media.get(id);
Media.prototype.node = null;
var prefix = args[1].split(':').shift();
var extension = srcUri.extension;
if (thisM.node === null) {
if (SUPPORTED_EXTENSIONS.indexOf(extension) === -1 && SUPPORTED_PREFIXES.indexOf(prefix) === -1) {
if (lose) {
lose({ code: MediaError.MEDIA_ERR_ABORTED });
}
return false; // unable to create
}
// Don't create Audio object in case of record mode
if (createAudioNode === true) {
thisM.node = new Audio();
thisM.node.msAudioCategory = 'BackgroundCapableMedia';
thisM.node.src = srcUri.absoluteCanonicalUri;
thisM.node.onloadstart = function () {
Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STARTING);
};
thisM.node.ontimeupdate = function (e) {
Media.onStatus(id, Media.MEDIA_POSITION, e.target.currentTime);
};
thisM.node.onplaying = function () {
Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_RUNNING);
};
thisM.node.ondurationchange = function (e) {
Media.onStatus(id, Media.MEDIA_DURATION, e.target.duration || -1);
};
thisM.node.onerror = function (e) {
// Due to media.spec.15 It should return MediaError for bad filename
var err =
e.target.error.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED
? { code: MediaError.MEDIA_ERR_ABORTED }
: e.target.error;
Media.onStatus(id, Media.MEDIA_ERROR, err);
};
thisM.node.onended = function () {
Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STOPPED);
};
}
}
return true; // successfully created
},
// Start playing the audio
startPlayingAudio: function (win, lose, args) {
var id = args[0];
// var src = args[1];
// var options = args[2];
var thisM = Media.get(id);
// if Media was released, then node will be null and we need to create it again
if (!thisM.node) {
args[2] = true; // Setting createAudioNode to true
if (!module.exports.create(win, lose, args)) {
// there is no reason to continue if we can't create media
// corresponding callback has been invoked in create so we don't need to call it here
return;
}
}
try {
thisM.node.play();
} catch (err) {
if (lose) {
lose({ code: MediaError.MEDIA_ERR_ABORTED });
}
}
},
// Stops the playing audio
stopPlayingAudio: function (win, lose, args) {
var id = args[0];
try {
var thisM = Media.get(id);
thisM.node.pause();
thisM.node.currentTime = 0;
Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STOPPED);
} catch (err) {
lose('Failed to stop: ' + err);
}
},
// Seeks to the position in the audio
seekToAudio: function (win, lose, args) {
var id = args[0];
var milliseconds = args[1];
var thisM = Media.get(id);
try {
thisM.node.currentTime = milliseconds / 1000;
win(thisM.node.currentTime);
} catch (err) {
lose('Failed to seek: ' + err);
}
},
// Pauses the playing audio
pausePlayingAudio: function (win, lose, args) {
var id = args[0];
var thisM = Media.get(id);
try {
thisM.node.pause();
Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_PAUSED);
} catch (err) {
lose('Failed to pause: ' + err);
}
},
// Gets current position in the audio
getCurrentPositionAudio: function (win, lose, args) {
var id = args[0];
try {
var p = Media.get(id).node.currentTime;
win(p);
} catch (err) {
lose(err);
}
},
// Start recording audio
startRecordingAudio: function (win, lose, args) {
var id = args[0];
var srcUri = processUri(args[1]);
var dest = parseUriToPathAndFilename(srcUri);
var destFileName = dest.fileName;
var success = function () {
Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_RUNNING);
};
var error = function (reason) {
Media.onStatus(id, Media.MEDIA_ERROR, reason);
};
// Initialize device
Media.prototype.mediaCaptureMgr = null;
var thisM = Media.get(id);
var captureInitSettings = new Windows.Media.Capture.MediaCaptureInitializationSettings();
captureInitSettings.streamingCaptureMode = Windows.Media.Capture.StreamingCaptureMode.audio;
thisM.mediaCaptureMgr = new Windows.Media.Capture.MediaCapture();
thisM.mediaCaptureMgr.addEventListener('failed', error);
thisM.mediaCaptureMgr.initializeAsync(captureInitSettings).done(function (result) {
thisM.mediaCaptureMgr.addEventListener('recordlimitationexceeded', error);
thisM.mediaCaptureMgr.addEventListener('failed', error);
// Start recording
Windows.Storage.ApplicationData.current.temporaryFolder
.createFileAsync(destFileName, Windows.Storage.CreationCollisionOption.replaceExisting)
.done(function (newFile) {
recordedFile = newFile;
var encodingProfile = null;
switch (newFile.fileType) {
case '.m4a':
encodingProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createM4a(
Windows.Media.MediaProperties.AudioEncodingQuality.auto
);
break;
case '.mp3':
encodingProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createMp3(
Windows.Media.MediaProperties.AudioEncodingQuality.auto
);
break;
case '.wma':
encodingProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createWma(
Windows.Media.MediaProperties.AudioEncodingQuality.auto
);
break;
default:
error('Invalid file type for record');
break;
}
thisM.mediaCaptureMgr.startRecordToStorageFileAsync(encodingProfile, newFile).done(success, error);
}, error);
}, error);
},
// Stop recording audio
stopRecordingAudio: function (win, lose, args) {
var id = args[0];
var thisM = Media.get(id);
var srcUri = processUri(thisM.src);
var dest = parseUriToPathAndFilename(srcUri);
var destPath = dest.path;
var destFileName = dest.fileName;
var fsType = dest.fsType;
var success = function () {
Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STOPPED);
};
var error = function (reason) {
Media.onStatus(id, Media.MEDIA_ERROR, reason);
};
thisM.mediaCaptureMgr.stopRecordAsync().done(function () {
if (fsType === fsTypes.TEMPORARY) {
if (!destPath) {
// if path is not defined, we leave recorded file in temporary folder (similar to iOS)
success();
} else {
Windows.Storage.ApplicationData.current.temporaryFolder.getFolderAsync(destPath).done(function (destFolder) {
recordedFile
.copyAsync(destFolder, destFileName, Windows.Storage.CreationCollisionOption.replaceExisting)
.done(success, error);
}, error);
}
} else {
// Copying file to persistent storage
if (!destPath) {
recordedFile
.copyAsync(
Windows.Storage.ApplicationData.current.localFolder,
destFileName,
Windows.Storage.CreationCollisionOption.replaceExisting
)
.done(success, error);
} else {
Windows.Storage.ApplicationData.current.localFolder.getFolderAsync(destPath).done(function (destFolder) {
recordedFile
.copyAsync(destFolder, destFileName, Windows.Storage.CreationCollisionOption.replaceExisting)
.done(success, error);
}, error);
}
}
}, error);
},
// Release the media object
release: function (win, lose, args) {
var id = args[0];
var thisM = Media.get(id);
try {
if (thisM.node) {
thisM.node.onloadedmetadata = null;
// Unsubscribing as the media object is being released
thisM.node.onerror = null;
// Needed to avoid "0x80070005 - JavaScript runtime error: Access is denied." on copyAsync
thisM.node.src = null;
delete thisM.node;
}
} catch (err) {
lose('Failed to release: ' + err);
}
},
setVolume: function (win, lose, args) {
var id = args[0];
var volume = args[1];
var thisM = Media.get(id);
thisM.volume = volume;
}
};
/**
* Converts a path to Windows.Foundation.Uri basing on App data temporary folder
* if scheme is not defined, e.g.: path/to/file.m4a -> ms-appdata:///temp/path/to/file.m4a
* @param {String} src Input path
* @return {Object} Windows.Foundation.Uri
*/
function setTemporaryFsByDefault (src) {
var uri;
try {
uri = new Windows.Foundation.Uri(src);
} catch (e) {
if (e.number === PARAMETER_IS_INCORRECT) {
// Use TEMPORARY fs there is no 'scheme:'
uri = new Windows.Foundation.Uri(tempFolderAppDataBasePath, src);
} else {
throw e;
}
}
return uri;
}
/**
* Convert native full path to ms-appdata path
* @param {Object} uri Windows.Foundation.Uri
* @return {Object} ms-appdata Windows.Foundation.Uri
*/
function fullPathToAppData (uri) {
if (uri.schemeName === 'file') {
if (uri.rawUri.indexOf(Windows.Storage.ApplicationData.current.localFolder.path) !== -1) {
// Also remove path' beginning slash to avoid losing folder name part
uri = new Windows.Foundation.Uri(
localFolderAppDataBasePath,
uri.rawUri.replace(localFolderFullPath, '').replace(/^[\\/]{1,2}/, '')
);
} else if (uri.rawUri.indexOf(Windows.Storage.ApplicationData.current.temporaryFolder.path) !== -1) {
uri = new Windows.Foundation.Uri(
tempFolderAppDataBasePath,
uri.rawUri.replace(tempFolderFullPath, '').replace(/^[\\/]{1,2}/, '')
);
} else {
throw new Error('Not supported file uri: ' + uri.rawUri);
}
}
return uri;
}
/**
* Converts cdvfile paths to ms-appdata path
* @param {Object} uri Input cdvfile scheme Windows.Foundation.Uri
* @return {Object} Windows.Foundation.Uri based on App data path
*/
function cdvfileToAppData (uri) {
var cdvFsRoot;
if (uri.schemeName === 'cdvfile') {
cdvFsRoot = uri.path.split('/')[1];
if (cdvFsRoot === 'temporary') {
return new Windows.Foundation.Uri(tempFolderAppDataBasePath, uri.path.split('/').slice(2).join('/'));
} else if (cdvFsRoot === 'persistent') {
return new Windows.Foundation.Uri(localFolderAppDataBasePath, uri.path.split('/').slice(2).join('/'));
} else {
throw new Error(cdvFsRoot + ' cdvfile root is not supported on Windows');
}
}
return uri;
}
/**
* Prepares media src for internal usage
* @param {String} src Input media path
* @return {Object} Windows.Foundation.Uri
*/
function processUri (src) {
// Collapse double slashes (File plugin issue): ms-appdata:///temp//recs/memos/media.m4a => ms-appdata:///temp/recs/memos/media.m4a
src = src.replace(/([^/:])(\/\/)([^/])/g, '$1/$3');
// Remove beginning slashes
src = src.replace(/^[\\/]{1,2}/, '');
var uri = setTemporaryFsByDefault(src);
uri = fullPathToAppData(uri);
uri = cdvfileToAppData(uri);
return uri;
}
/**
* Extracts path, filename and filesystem type from Uri
* @param {Object} uri Windows.Foundation.Uri
* @return {Object} Object containing path, filename and filesystem type
*/
function parseUriToPathAndFilename (uri) {
// Removing scheme and location, using backslashes: ms-appdata:///local/path/to/file.m4a -> path\\to\\file.m4a
var normalizedSrc = uri.path.split('/').slice(2).join('\\');
var path = normalizedSrc.substr(0, normalizedSrc.lastIndexOf('\\'));
var fileName = normalizedSrc.replace(path + '\\', '');
var fsType;
if (uri.path.split('/')[1] === 'local') {
fsType = fsTypes.PERSISTENT;
} else if (uri.path.split('/')[1] === 'temp') {
fsType = fsTypes.TEMPORARY;
}
return {
path: path,
fileName: fileName,
fsType: fsType
};
}
require('cordova/exec/proxy').add('Media', module.exports);