import 'dart:async'; import 'dart:core'; import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:flutter_sound/android_encoder.dart'; import 'package:flutter_sound/ios_quality.dart'; class FlutterSound { static const MethodChannel _channel = const MethodChannel('flutter_sound'); static StreamController _recorderController; static StreamController _dbPeakController; static StreamController _playerController; /// Value ranges from 0 to 120 Stream get onRecorderDbPeakChanged => _dbPeakController.stream; Stream get onRecorderStateChanged => _recorderController.stream; Stream get onPlayerStateChanged => _playerController.stream; bool get isPlaying => _isPlaying; bool get isRecording => _isRecording; bool _isRecording = false; bool _isPlaying = false; Future setSubscriptionDuration(double sec) async { String result = await _channel .invokeMethod('setSubscriptionDuration', { 'sec': sec, }); return result; } Future _setRecorderCallback() async { if (_recorderController == null) { _recorderController = new StreamController.broadcast(); } if (_dbPeakController == null) { _dbPeakController = new StreamController.broadcast(); } _channel.setMethodCallHandler((MethodCall call) { switch (call.method) { case "updateRecorderProgress": Map result = json.decode(call.arguments); if (_recorderController != null) _recorderController.add(new RecordStatus.fromJSON(result)); break; case "updateDbPeakProgress": if (_dbPeakController!= null) _dbPeakController.add(call.arguments); break; default: throw new ArgumentError('Unknown method ${call.method} '); } return null; }); } Future _setPlayerCallback() async { if (_playerController == null) { _playerController = new StreamController.broadcast(); } _channel.setMethodCallHandler((MethodCall call) { switch (call.method) { case "updateProgress": Map result = jsonDecode(call.arguments); if (_playerController!=null) _playerController.add(new PlayStatus.fromJSON(result)); break; case "audioPlayerDidFinishPlaying": Map result = jsonDecode(call.arguments); PlayStatus status = new PlayStatus.fromJSON(result); if (status.currentPosition != status.duration) { status.currentPosition = status.duration; } if (_playerController != null) _playerController.add(status); this._isPlaying = false; _removePlayerCallback(); break; default: throw new ArgumentError('Unknown method ${call.method}'); } return null; }); } Future _removeRecorderCallback() async { if (_recorderController != null) { _recorderController ..add(null) ..close(); _recorderController = null; } } Future _removeDbPeakCallback() async { if (_dbPeakController != null) { _dbPeakController ..add(null) ..close(); _dbPeakController = null; } } Future _removePlayerCallback() async { if (_playerController != null) { _playerController ..add(null) ..close(); _playerController = null; } } Future startRecorder(String uri, {int sampleRate, int numChannels, int bitRate, AndroidEncoder androidEncoder = AndroidEncoder.AAC, AndroidAudioSource androidAudioSource = AndroidAudioSource.MIC, AndroidOutputFormat androidOutputFormat = AndroidOutputFormat.MPEG_4, IosQuality iosQuality = IosQuality.LOW, }) async { if (this._isRecording) { throw new RecorderRunningException('Recorder is already recording.'); } try { String result = await _channel.invokeMethod('startRecorder', { 'path': uri, 'sampleRate': sampleRate, 'numChannels': numChannels, 'bitRate': bitRate, 'androidEncoder': androidEncoder?.value, 'androidAudioSource': androidAudioSource?.value, 'androidOutputFormat': androidOutputFormat?.value, 'iosQuality': iosQuality?.value }); _setRecorderCallback(); this._isRecording = true; return result; } catch (err) { throw new Exception(err); } } Future stopRecorder() async { if (!this._isRecording) { throw new RecorderStoppedException('Recorder is already stopped.'); } String result = await _channel.invokeMethod('stopRecorder'); this._isRecording = false; _removeRecorderCallback(); _removeDbPeakCallback(); return result; } Future startPlayer(String uri) async { if (this._isPlaying) { this.resumePlayer(); return 'Player resumed'; // throw PlayerRunningException('Player is already playing.'); } try { String result = await _channel.invokeMethod('startPlayer', { 'path': uri, }); print('startPlayer result: $result'); _setPlayerCallback(); this._isPlaying = true; return result; } catch (err) { throw Exception(err); } } Future stopPlayer() async { if (!this._isPlaying) { throw PlayerStoppedException('Player already stopped.'); } this._isPlaying = false; String result = await _channel.invokeMethod('stopPlayer'); _removePlayerCallback(); return result; } Future pausePlayer() async { try { String result = await _channel.invokeMethod('pausePlayer'); return result; } catch (err) { print('err: $err'); return err; } } Future resumePlayer() async { try { String result = await _channel.invokeMethod('resumePlayer'); return result; } catch (err) { print('err: $err'); return err; } } Future seekToPlayer(int milliSecs) async { try { String result = await _channel.invokeMethod('seekToPlayer', { 'sec': milliSecs, }); return result; } catch (err) { print('err: $err'); return err; } } Future setVolume(double volume) async { String result = ''; if (volume < 0.0 || volume > 1.0) { result = 'Value of volume should be between 0.0 and 1.0.'; return result; } result = await _channel .invokeMethod('setVolume', { 'volume': volume, }); return result; } /// Defines the interval at which the peak level should be updated. /// Default is 0.8 seconds Future setDbPeakLevelUpdate(double intervalInSecs) async { String result = await _channel .invokeMethod('setDbPeakLevelUpdate', { 'intervalInSecs': intervalInSecs, }); return result; } /// Enables or disables processing the Peak level in db's. Default is disabled Future setDbLevelEnabled(bool enabled) async { String result = await _channel .invokeMethod('setDbLevelEnabled', { 'enabled': enabled, }); return result; } } class RecordStatus { final double currentPosition; RecordStatus.fromJSON(Map json) : currentPosition = double.parse(json['current_position']); @override String toString() { return 'currentPosition: $currentPosition'; } } class PlayStatus { final double duration; double currentPosition; PlayStatus.fromJSON(Map json) : duration = double.parse(json['duration']), currentPosition = double.parse(json['current_position']); @override String toString() { return 'duration: $duration, ' 'currentPosition: $currentPosition'; } } class PlayerRunningException implements Exception { final String message; PlayerRunningException(this.message); } class PlayerStoppedException implements Exception { final String message; PlayerStoppedException(this.message); } class RecorderRunningException implements Exception { final String message; RecorderRunningException(this.message); } class RecorderStoppedException implements Exception { final String message; RecorderStoppedException(this.message); }