iOS implementation

This commit is contained in:
Ryan Heise 2019-12-01 02:28:17 +11:00
parent 8569c34ce2
commit 687553c875
13 changed files with 554 additions and 19 deletions

View File

@ -34,7 +34,7 @@ public class AudioPlayer implements MethodCallHandler, MediaPlayer.OnCompletionL
}
};
private final long id;
private final String id;
private final MediaPlayer player;
private PlaybackState state;
private PlaybackState stateBeforeSeek;
@ -42,7 +42,7 @@ public class AudioPlayer implements MethodCallHandler, MediaPlayer.OnCompletionL
private int updatePosition;
private Integer seekPos;
public AudioPlayer(final Registrar registrar, final long id) {
public AudioPlayer(final Registrar registrar, final String id) {
this.registrar = registrar;
this.id = id;
methodChannel = new MethodChannel(registrar.messenger(), "com.ryanheise.just_audio.methods." + id);

View File

@ -24,7 +24,7 @@ public class JustAudioPlugin implements MethodCallHandler {
public void onMethodCall(MethodCall call, Result result) {
switch (call.method) {
case "init":
long id = (Long)call.arguments;
String id = (String)call.arguments;
new AudioPlayer(registrar, id);
result.success(null);
break;

View File

@ -1 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@ -1 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

72
example/ios/Podfile Normal file
View File

@ -0,0 +1,72 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
pods_ary = []
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) { |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
pods_ary.push({:name => podname, :path => podpath});
else
puts "Invalid plugin specification: #{line}"
end
}
return pods_ary
end
target 'Runner' do
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
# Flutter Pods
generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
if generated_xcode_build_settings.empty?
puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first."
end
generated_xcode_build_settings.map { |p|
if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
symlink = File.join('.symlinks', 'flutter')
File.symlink(File.dirname(p[:path]), symlink)
pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
end
}
# Plugin Pods
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.map { |p|
symlink = File.join('.symlinks', 'plugins', p[:name])
File.symlink(p[:path], symlink)
pod p[:name], :path => File.join(symlink, 'ios')
}
end
# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
install! 'cocoapods', :disable_input_output_paths => true
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end

28
example/ios/Podfile.lock Normal file
View File

@ -0,0 +1,28 @@
PODS:
- Flutter (1.0.0)
- just_audio (0.0.1):
- Flutter
- path_provider (0.0.1):
- Flutter
DEPENDENCIES:
- Flutter (from `.symlinks/flutter/ios`)
- just_audio (from `.symlinks/plugins/just_audio/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
EXTERNAL SOURCES:
Flutter:
:path: ".symlinks/flutter/ios"
just_audio:
:path: ".symlinks/plugins/just_audio/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
SPEC CHECKSUMS:
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
just_audio: c695d6e7e37f9e96672dd84039d7530e7fd5c205
path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d
PODFILE CHECKSUM: 7fb83752f59ead6285236625b82473f90b1cb932
COCOAPODS: 1.7.5

View File

@ -19,6 +19,7 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
D06FA586B72D3A4E8145F7B3 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C5F18129E1310C9DA1B65F44 /* libPods-Runner.a */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -39,11 +40,13 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
2920064AACAD73E894573C6E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
936C8FBACDB1725D477088CC /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
@ -53,6 +56,8 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C5F18129E1310C9DA1B65F44 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
EEEB488F061389F2C0725BDD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -62,12 +67,21 @@
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
D06FA586B72D3A4E8145F7B3 /* libPods-Runner.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1E7998A536E2BAD21DDFF12E /* Frameworks */ = {
isa = PBXGroup;
children = (
C5F18129E1310C9DA1B65F44 /* libPods-Runner.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@ -87,7 +101,8 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
A27F1C3EF07264C52FFA0B86 /* Pods */,
1E7998A536E2BAD21DDFF12E /* Frameworks */,
);
sourceTree = "<group>";
};
@ -123,6 +138,17 @@
name = "Supporting Files";
sourceTree = "<group>";
};
A27F1C3EF07264C52FFA0B86 /* Pods */ = {
isa = PBXGroup;
children = (
EEEB488F061389F2C0725BDD /* Pods-Runner.debug.xcconfig */,
2920064AACAD73E894573C6E /* Pods-Runner.release.xcconfig */,
936C8FBACDB1725D477088CC /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -130,12 +156,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
3D5B4DF09BB47DFA6E4B8495 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
A38593E728AFE0DAE382B1D1 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -208,6 +236,28 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
3D5B4DF09BB47DFA6E4B8495 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -222,6 +272,21 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
A38593E728AFE0DAE382B1D1 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */

View File

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,6 +1,20 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.10"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.2"
async:
dependency: transitive
description:
@ -8,13 +22,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
just_audio:
dependency: "direct dev"
description:
path: ".."
relative: true
source: path
version: "0.0.1"
boolean_selector:
dependency: transitive
description:
@ -36,6 +43,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.11"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
cupertino_icons:
dependency: "direct main"
description:
@ -53,6 +74,20 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
image:
dependency: transitive
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
just_audio:
dependency: "direct dev"
description:
path: ".."
relative: true
source: path
version: "0.0.1"
matcher:
dependency: transitive
description:
@ -88,6 +123,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0+1"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
platform:
dependency: transitive
description:
@ -170,6 +212,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "3.5.0"
sdks:
dart: ">=2.2.2 <3.0.0"
dart: ">=2.4.0 <3.0.0"
flutter: ">=1.9.1+hotfix.5 <2.0.0"

16
ios/Classes/AudioPlayer.h Normal file
View File

@ -0,0 +1,16 @@
#import <Flutter/Flutter.h>
@interface AudioPlayer : NSObject<FlutterStreamHandler>
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam;
@end
enum PlaybackState {
none,
stopped,
paused,
playing,
buffering,
connecting
};

286
ios/Classes/AudioPlayer.m Normal file
View File

@ -0,0 +1,286 @@
#import "AudioPlayer.h"
#import <AVFoundation/AVFoundation.h>
// TODO: Check for and report invalid state transitions.
@implementation AudioPlayer {
NSObject<FlutterPluginRegistrar>* _registrar;
FlutterMethodChannel* _methodChannel;
FlutterEventChannel* _eventChannel;
FlutterEventSink _eventSink;
NSString* _playerId;
AVPlayer* _player;
enum PlaybackState _state;
enum PlaybackState _stateBeforeSeek;
long long _updateTime;
int _updatePosition;
int _seekPos;
FlutterResult _connectionResult;
id _endObserver;
id _timeObserver;
}
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam {
self = [super init];
NSAssert(self, @"super init cannot be nil");
_registrar = registrar;
_playerId = idParam;
_methodChannel = [FlutterMethodChannel
methodChannelWithName:[NSMutableString stringWithFormat:@"com.ryanheise.just_audio.methods.%@", _playerId]
binaryMessenger:[registrar messenger]];
_eventChannel = [FlutterEventChannel
eventChannelWithName:[NSMutableString stringWithFormat:@"com.ryanheise.just_audio.events.%@", _playerId]
binaryMessenger:[registrar messenger]];
[_eventChannel setStreamHandler:self];
_state = none;
_stateBeforeSeek = none;
_player = nil;
_seekPos = -1;
_endObserver = 0;
_timeObserver = 0;
__weak __typeof__(self) weakSelf = self;
[_methodChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
[weakSelf handleMethodCall:call result:result];
}];
return self;
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
NSArray* args = (NSArray*)call.arguments;
if ([@"setUrl" isEqualToString:call.method]) {
[self setUrl:args[0] result:result];
} else if ([@"play" isEqualToString:call.method]) {
[self play:args[0]];
result(nil);
} else if ([@"pause" isEqualToString:call.method]) {
[self pause];
result(nil);
} else if ([@"stop" isEqualToString:call.method]) {
[self stop];
result(nil);
} else if ([@"setVolume" isEqualToString:call.method]) {
[self setVolume:(float)[args[0] doubleValue]];
result(nil);
} else if ([@"seek" isEqualToString:call.method]) {
[self seek:[args[0] intValue] result:result];
result(nil);
} else if ([@"dispose" isEqualToString:call.method]) {
[self dispose];
result(nil);
} else {
result(FlutterMethodNotImplemented);
}
// TODO
/* } catch (Exception e) { */
/* e.printStackTrace(); */
/* result.error("Error", null, null); */
/* } */
}
- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink {
_eventSink = eventSink;
return nil;
}
- (FlutterError*)onCancelWithArguments:(id)arguments {
_eventSink = nil;
return nil;
}
- (void)checkForDiscontinuity {
if (!_eventSink) return;
if (_state != playing && _state != buffering) return;
long long now = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
int position = [self getCurrentPosition];
long long timeSinceLastUpdate = now - _updateTime;
long long expectedPosition = _updatePosition + timeSinceLastUpdate;
long long drift = position - expectedPosition;
// Update if we've drifted or just started observing
if (_updateTime == 0L) {
[self broadcastPlayerState];
} else if (drift < -100) {
NSLog(@"time discontinuity detected: %lld", drift);
[self setPlaybackState:buffering];
} else if (_state == buffering) {
[self setPlaybackState:playing];
}
}
- (void)broadcastPlayerState {
long long now = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
_updatePosition = [self getCurrentPosition];
_updateTime = now;
_eventSink(@[
// state
@(_state),
// updatePosition
@(_updatePosition),
// updateTime
@(_updateTime),
]);
}
- (int)getCurrentPosition {
if (_state == none || _state == connecting) {
return 0;
} else if (_seekPos != -1) {
return _seekPos;
} else {
return (int)(1000 * CMTimeGetSeconds([_player currentTime]));
}
}
- (void)setPlaybackState:(enum PlaybackState)state {
//enum PlaybackState oldState = _state;
_state = state;
// TODO: Investigate when we need to start and stop
// observing item position.
/* if (oldState != playing && state == playing) { */
/* [self startObservingPosition]; */
/* } */
[self broadcastPlayerState];
}
- (void)setUrl:(NSString*)url result:(FlutterResult)result {
// TODO: error if already connecting
_connectionResult = result;
[self setPlaybackState:connecting];
if (_player) {
[[_player currentItem] removeObserver:self forKeyPath:@"status"];
[[NSNotificationCenter defaultCenter] removeObserver:_endObserver];
_endObserver = 0;
}
AVPlayerItem* playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:url]];
[playerItem addObserver:self
forKeyPath:@"status"
options:NSKeyValueObservingOptionNew
context:nil];
// TODO: Add observer for _endObserver.
_endObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:AVPlayerItemDidPlayToEndTimeNotification
object:playerItem
queue:nil
usingBlock:^(NSNotification* note) {
NSLog(@"Reached play end time");
[self setPlaybackState:stopped];
}
];
if (_player) {
[_player replaceCurrentItemWithPlayerItem:playerItem];
} else {
_player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
}
if (_timeObserver) {
[_player removeTimeObserver:_timeObserver];
_timeObserver = 0;
}
// TODO: learn about the different ways to define weakSelf.
//__weak __typeof__(self) weakSelf = self;
//typeof(self) __weak weakSelf = self;
__unsafe_unretained typeof(self) weakSelf = self;
_timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(200, 1000)
queue:nil
usingBlock:^(CMTime time) {
[weakSelf checkForDiscontinuity];
}
];
// We send result after the playerItem is ready in observeValueForKeyPath.
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context {
if ([keyPath isEqualToString:@"status"]) {
AVPlayerItemStatus status = AVPlayerItemStatusUnknown;
NSNumber *statusNumber = change[NSKeyValueChangeNewKey];
if ([statusNumber isKindOfClass:[NSNumber class]]) {
status = statusNumber.integerValue;
}
switch (status) {
case AVPlayerItemStatusReadyToPlay:
[self setPlaybackState:stopped];
_connectionResult(@((int)(1000 * CMTimeGetSeconds([[_player currentItem] duration]))));
break;
case AVPlayerItemStatusFailed:
NSLog(@"AVPlayerItemStatusFailed");
_connectionResult(nil);
break;
case AVPlayerItemStatusUnknown:
break;
}
}
}
- (void)play:(NSNumber*)untilPosition {
// TODO: dynamically adjust the lag.
//int lag = 6;
int start = [self getCurrentPosition];
if (untilPosition != [NSNull null] && [untilPosition intValue] <= start) {
return;
}
[_player play];
[self setPlaybackState:playing];
// TODO: convert this Android code to iOS
/* if (endDetector != null) { */
/* handler.removeCallbacks(endDetector); */
/* } */
/* if (untilPosition != null) { */
/* final int duration = Math.max(0, untilPosition - start - lag); */
/* handler.postDelayed(new Runnable() { */
/* @Override */
/* public void run() { */
/* final int position = getCurrentPosition(); */
/* if (position > untilPosition - 20) { */
/* pause(); */
/* } else { */
/* final int duration = Math.max(0, untilPosition - position - lag); */
/* handler.postDelayed(this, duration); */
/* } */
/* } */
/* }, duration); */
/* } */
}
- (void)pause {
[_player pause];
[self setPlaybackState:paused];
}
- (void)stop {
[_player pause];
[[_player currentItem] seekToTime:CMTimeMake(0, 1000)];
[self setPlaybackState:stopped];
}
- (void)setVolume:(float)volume {
[_player setVolume:volume];
}
- (void)seek:(int)position result:(FlutterResult)result {
_stateBeforeSeek = _state;
_seekPos = position;
NSLog(@"seek. enter buffering");
[self setPlaybackState:buffering];
[_player seekToTime:CMTimeMake(position, 1000)
completionHandler:^(BOOL finished) {
NSLog(@"seek completed");
[self onSeekCompletion:result];
}];
}
- (void)onSeekCompletion:(FlutterResult)result {
_seekPos = -1;
[self setPlaybackState:_stateBeforeSeek];
_stateBeforeSeek = none;
result(nil);
}
- (void)dispose {
if (_state != none) {
[self stop];
[self setPlaybackState:none];
}
}
@end

View File

@ -1,17 +1,31 @@
#import "JustAudioPlugin.h"
#import "AudioPlayer.h"
#import "AudioPlayer.h"
@implementation JustAudioPlugin {
NSObject<FlutterPluginRegistrar>* _registrar;
}
@implementation JustAudioPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"just_audio"
methodChannelWithName:@"com.ryanheise.just_audio.methods"
binaryMessenger:[registrar messenger]];
JustAudioPlugin* instance = [[JustAudioPlugin alloc] init];
JustAudioPlugin* instance = [[JustAudioPlugin alloc] initWithRegistrar:registrar];
[registrar addMethodCallDelegate:instance channel:channel];
}
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
self = [super init];
NSAssert(self, @"super init cannot be nil");
_registrar = registrar;
return self;
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([@"getPlatformVersion" isEqualToString:call.method]) {
result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
if ([@"init" isEqualToString:call.method]) {
NSString* playerId = call.arguments;
AudioPlayer* player = [[AudioPlayer alloc] initWithRegistrar:_registrar playerId:playerId];
result(nil);
} else {
result(FlutterMethodNotImplemented);
}

View File

@ -48,7 +48,7 @@ class AudioPlayer {
MethodChannel('com.ryanheise.just_audio.methods');
static Future<MethodChannel> _createChannel(int id) async {
await _mainChannel.invokeMethod('init', id);
await _mainChannel.invokeMethod('init', '$id');
return MethodChannel('com.ryanheise.just_audio.methods.$id');
}