| /* |
| * |
| * 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); |