diff --git a/CHANGELOG.md b/CHANGELOG.md index f99ed46..e5ddfc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## 2.0.1 [June 12, 2021] + +* Set iOS deployment target to 9.0 +* Fixed observed value unhandled exception in iOS implementation + +## 2.0.0 [June 11, 2021] + +* Migrated to null-safety + +## 1.0.43 [June 8, 2021] + +* Updated iOS deployment target to 9.0 + +## 1.0.42 [November 10, 2020] + +* Merged [PR](https://github.com/muslimtv/flutter_playout/pull/70) + +## 1.0.41 [September 13, 2020] + +* removed isEmpty check when setting text track language + ## 1.0.40 [September 7, 2020] * added support for text tracks for Android diff --git a/LICENSE.md b/LICENSE similarity index 100% rename from LICENSE.md rename to LICENSE diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies index 2c40f70..e1429f2 100644 --- a/example/.flutter-plugins-dependencies +++ b/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_playout","path":"/Users/Khuram/Projects/flutter_playout/","dependencies":[]}],"android":[{"name":"flutter_playout","path":"/Users/Khuram/Projects/flutter_playout/","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"flutter_playout","dependencies":[]}],"date_created":"2020-09-07 14:04:25.413425","version":"1.20.3"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_playout","path":"/Users/Khuram/Projects/flutter_playout/","dependencies":[]}],"android":[{"name":"flutter_playout","path":"/Users/Khuram/Projects/flutter_playout/","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"flutter_playout","dependencies":[]}],"date_created":"2021-06-11 23:49:06.068825","version":"2.2.1"} \ No newline at end of file diff --git a/example/README.md b/example/README.md index df629a9..c65a8fe 100644 --- a/example/README.md +++ b/example/README.md @@ -239,6 +239,7 @@ class _VideoPlayoutState extends State languageCode: "fr", uri: "https://texttracks.example.com/french.vtt"), ], + loop: false, ), ), /* multi language menu */ diff --git a/example/ios/Flutter/.last_build_id b/example/ios/Flutter/.last_build_id index 81dd376..dd2e70b 100644 --- a/example/ios/Flutter/.last_build_id +++ b/example/ios/Flutter/.last_build_id @@ -1 +1 @@ -46421090b226fe359c3c6937c1ad9913 \ No newline at end of file +7e31c1523c8179c5a7977e376350a63f \ No newline at end of file diff --git a/example/ios/Flutter/Flutter.podspec b/example/ios/Flutter/Flutter.podspec index 5ca3041..2c4421c 100644 --- a/example/ios/Flutter/Flutter.podspec +++ b/example/ios/Flutter/Flutter.podspec @@ -1,18 +1,18 @@ # # NOTE: This podspec is NOT to be published. It is only used as a local source! +# This is a generated file; do not edit or check into version control. # Pod::Spec.new do |s| s.name = 'Flutter' s.version = '1.0.0' s.summary = 'High-performance, high-fidelity mobile apps.' - s.description = <<-DESC -Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. - DESC s.homepage = 'https://flutter.io' s.license = { :type => 'MIT' } s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } s.ios.deployment_target = '8.0' - s.vendored_frameworks = 'Flutter.framework' + # Framework linking is handled by Flutter tooling, not CocoaPods. + # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs. + s.vendored_frameworks = 'path/to/nothing' end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 4914249..40e9a18 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -14,9 +14,9 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_playout/ios" SPEC CHECKSUMS: - Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c flutter_playout: a5aed32b3003b02eb4432815ecfe0c0e42a801ff PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c -COCOAPODS: 1.8.3 +COCOAPODS: 1.10.0 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 61c0645..2014a6d 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -263,12 +263,10 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../Flutter/Flutter.framework", "${BUILT_PRODUCTS_DIR}/flutter_playout/flutter_playout.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_playout.framework", ); runOnlyForDeploymentPostprocessing = 0; @@ -351,7 +349,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -434,7 +432,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -483,7 +481,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a1..919434a 100644 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/example/lib/video.dart b/example/lib/video.dart index 04e874b..3236471 100644 --- a/example/lib/video.dart +++ b/example/lib/video.dart @@ -22,7 +22,7 @@ class VideoPlayout extends StatefulWidget { class _VideoPlayoutState extends State with PlayerObserver, MultiAudioSupport { final String _url = null; - List _hlsLanguages = List(); + List _hlsLanguages = []; @override void initState() { @@ -51,12 +51,13 @@ class _VideoPlayoutState extends State title: "MTA International", subtitle: "Reaching The Corners Of The Earth", preferredAudioLanguage: "eng", - isLiveStream: true, + isLiveStream: false, position: 0, url: _url, onViewCreated: _onViewCreated, desiredState: widget.desiredState, preferredTextLanguage: "en", + loop: false, ), ), /* multi language menu */ diff --git a/example/pubspec.lock b/example/pubspec.lock index c592964..0fe0d44 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,42 +7,42 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.2" + version: "2.6.1" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.2.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.13" + version: "1.15.0" cupertino_icons: dependency: "direct main" description: @@ -56,7 +56,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -68,7 +68,7 @@ packages: path: ".." relative: true source: path - version: "1.0.39" + version: "1.0.44" flutter_test: dependency: "direct dev" description: flutter @@ -94,21 +94,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.8" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" pedantic: dependency: transitive description: @@ -127,55 +127,56 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.1" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.5" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.17" + version: "0.3.0" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0" sdks: - dart: ">=2.9.0-14.0.dev <3.0.0" + dart: ">=2.12.0 <3.0.0" + flutter: ">=1.12.13+hotfix.6" diff --git a/ios/Classes/AudioPlayer.swift b/ios/Classes/AudioPlayer.swift index 8803312..ffdb56d 100644 --- a/ios/Classes/AudioPlayer.swift +++ b/ios/Classes/AudioPlayer.swift @@ -160,7 +160,11 @@ class AudioPlayer: NSObject, FlutterPlugin, FlutterStreamHandler { /* Add observer for AVPlayer status and AVPlayerItem status */ self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.status), options: [.new, .initial], context: nil) self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options:[.old, .new, .initial], context: nil) - self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.timeControlStatus), options:[.old, .new, .initial], context: nil) + if #available(iOS 10.0, *) { + self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.timeControlStatus), options:[.old, .new, .initial], context: nil) + } else { + // Fallback on earlier versions + } let interval = CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) @@ -201,48 +205,47 @@ class AudioPlayer: NSObject, FlutterPlugin, FlutterStreamHandler { /* Observe If AVPlayerItem.status Changed to Fail */ override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { - if keyPath == #keyPath(AVPlayerItem.status) { - - let newStatus: AVPlayerItem.Status - - if let newStatusAsNumber = change?[NSKeyValueChangeKey.newKey] as? NSNumber { - newStatus = AVPlayerItem.Status(rawValue: newStatusAsNumber.intValue)! - } else { - newStatus = .unknown - } - - if newStatus == .failed { - self.flutterEventSink?(["name":"onError", "error":(String(describing: self.audioPlayer.currentItem?.error))]) - } else if newStatus == .readyToPlay { - self.flutterEventSink?(["name":"onReady"]) - } - } - - else if keyPath == #keyPath(AVPlayer.timeControlStatus) { - - guard let p = object as! AVPlayer? else { - return + if keyPath == #keyPath(AVPlayerItem.status) { + + let newStatus: AVPlayerItem.Status + + if let newStatusAsNumber = change?[NSKeyValueChangeKey.newKey] as? NSNumber { + newStatus = AVPlayerItem.Status(rawValue: newStatusAsNumber.intValue)! + } else { + newStatus = .unknown + } + + if newStatus == .failed { + self.flutterEventSink?(["name":"onError", "error":(String(describing: self.audioPlayer.currentItem?.error))]) + } else if newStatus == .readyToPlay { + self.flutterEventSink?(["name":"onReady"]) + } } - if #available(iOS 10.0, *) { + if #available(iOS 10.0, *) { + if keyPath == #keyPath(AVPlayer.timeControlStatus) { + + guard let p = object as! AVPlayer? else { + return + } switch (p.timeControlStatus) { - case AVPlayerTimeControlStatus.paused: + case AVPlayer.TimeControlStatus.paused: self.flutterEventSink?(["name":"onPause"]) break - - case AVPlayerTimeControlStatus.playing: + + case AVPlayer.TimeControlStatus.playing: self.flutterEventSink?(["name":"onPlay"]) break - + case .waitingToPlayAtSpecifiedRate: break @unknown default: break } - } else { - // Fallback on earlier versions } + } else { + // Fallback on earlier versions } } diff --git a/ios/Classes/VideoPlayer.swift b/ios/Classes/VideoPlayer.swift index 8e51335..c23b15f 100644 --- a/ios/Classes/VideoPlayer.swift +++ b/ios/Classes/VideoPlayer.swift @@ -247,7 +247,9 @@ class VideoPlayer: NSObject, FlutterPlugin, FlutterStreamHandler, FlutterPlatfor /* Add observer for AVPlayer status and AVPlayerItem status */ self.player?.addObserver(self, forKeyPath: #keyPath(AVPlayer.status), options: [.new, .initial], context: nil) self.player?.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options:[.old, .new, .initial], context: nil) - self.player?.addObserver(self, forKeyPath: #keyPath(AVPlayer.timeControlStatus), options:[.old, .new, .initial], context: nil) + if #available(iOS 10.0, *) { + self.player?.addObserver(self, forKeyPath: #keyPath(AVPlayer.timeControlStatus), options:[.old, .new, .initial], context: nil) + } /* setup callback for onTime */ let interval = CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) @@ -355,32 +357,54 @@ class VideoPlayer: NSObject, FlutterPlugin, FlutterStreamHandler, FlutterPlatfor } } - else if keyPath == #keyPath(AVPlayer.timeControlStatus) { - + if (keyPath == #keyPath(AVPlayer.status)) { guard let p = object as! AVPlayer? else { return } - if #available(iOS 10.0, *) { + switch (p.status) { + case .readyToPlay: + break + case .unknown: + break + case .failed: + break + @unknown default: + break + } + } + + else if #available(iOS 10.0, *) { + if keyPath == #keyPath(AVPlayer.timeControlStatus) { + + guard let p = object as! AVPlayer? else { + return + } switch (p.timeControlStatus) { - case AVPlayerTimeControlStatus.paused: + case AVPlayer.TimeControlStatus.paused: isPlaying = false self.flutterEventSink?(["name":"onPause"]) break - - case AVPlayerTimeControlStatus.playing: + + case AVPlayer.TimeControlStatus.playing: isPlaying = true self.flutterEventSink?(["name":"onPlay"]) break - + case .waitingToPlayAtSpecifiedRate: break @unknown default: break } + + } else { + super.observeValue(forKeyPath: keyPath, + of: object, + change: change, + context: context) + return } - } else { super.observeValue(forKeyPath: keyPath, of: object, diff --git a/lib/audio.dart b/lib/audio.dart index 061ee50..9044b13 100644 --- a/lib/audio.dart +++ b/lib/audio.dart @@ -7,19 +7,19 @@ class Audio { Audio._(); - static Audio _instance; - static Audio instance() { + static Audio? _instance; + static Audio? instance() { if (_instance == null) { _instance = Audio._(); } return _instance; } - String _url; - String _title; - String _subtitle; - Duration _position; - bool _isLiveStream; + String? _url; + String? _title; + String? _subtitle; + Duration? _position; + bool? _isLiveStream; /// Plays given [url] with native player. The [title] and [subtitle] /// are used for lock screen info panel on both iOS & Android. Optionally pass diff --git a/lib/multiaudio/HLSManifestLanguage.dart b/lib/multiaudio/HLSManifestLanguage.dart index 94bb7a2..59123c3 100644 --- a/lib/multiaudio/HLSManifestLanguage.dart +++ b/lib/multiaudio/HLSManifestLanguage.dart @@ -1,8 +1,8 @@ class HLSManifestLanguage { - final String code; - final String name; - final String nativeName; - String url; + final String? code; + final String? name; + final String? nativeName; + String? url; HLSManifestLanguage(this.code, this.name, {this.nativeName, this.url}); @@ -25,7 +25,6 @@ class HLSManifestLanguage { } static List toJsonFromList(List languages) { - if (languages == null) return List(); return languages.map((a) => a.toJson()).toList(); } } diff --git a/lib/multiaudio/MultiAudioSupport.dart b/lib/multiaudio/MultiAudioSupport.dart index beafad8..1090915 100644 --- a/lib/multiaudio/MultiAudioSupport.dart +++ b/lib/multiaudio/MultiAudioSupport.dart @@ -6,7 +6,7 @@ import 'package:flutter/services.dart'; /// for HLS manifest. Use [setPreferredAudioLanguage] to set/change /// language for the currently playing asset. mixin MultiAudioSupport { - MethodChannel _methodChannel; + MethodChannel? _methodChannel; Future enableMultiAudioSupport(int viewId) async { _methodChannel = MethodChannel("tv.mta/NativeVideoPlayerMethodChannel_$viewId"); @@ -16,11 +16,8 @@ mixin MultiAudioSupport { /// is based on ISO_639_2 language codes. Please see /// [lib/multiaudio/ISO_639_2_LanguageCode.dart] void setPreferredAudioLanguage(String languageCode) async { - if (_methodChannel != null && - languageCode != null && - languageCode.isNotEmpty && - !Platform.isIOS) { - _methodChannel.invokeListMethod( + if (_methodChannel != null && languageCode.isNotEmpty && !Platform.isIOS) { + _methodChannel!.invokeListMethod( "setPreferredAudioLanguage", {"code": languageCode}); } } diff --git a/lib/player_observer.dart b/lib/player_observer.dart index f98b8b0..234fc12 100644 --- a/lib/player_observer.dart +++ b/lib/player_observer.dart @@ -28,24 +28,24 @@ mixin PlayerObserver { /// Override this method to get update when playhead moves. This method /// fires every second with [position] as seconds. - void onTime(int position) {/* user implementation */} + void onTime(int? position) {/* user implementation */} /// Override this method to get notifications when a seek operation has /// finished. This will occur when user finishes scrubbing media. /// [position] is position in seconds before seek started. /// [offset] is seconds after seek processed. - void onSeek(int position, double offset) {/* user implementation */} + void onSeek(int? position, double offset) {/* user implementation */} /// Override this method to get notifications when media duration is /// set or changed. /// [duration] is in milliseconds. Returns -1 for live stream - void onDuration(int duration) {/* user implementation */} + void onDuration(int? duration) {/* user implementation */} /// Override this method to get errors thrown by the player - void onError(String error) {/* user implementation */} + void onError(String? error) {/* user implementation */} void _processEvent(dynamic event) async { - String eventName = event["name"]; + String? eventName = event["name"]; switch (eventName) { @@ -73,7 +73,7 @@ mixin PlayerObserver { case "onSeek": /* position of the player before the player seeks (in seconds) */ - int position = (event["position"]).toInt(); + int? position = (event["position"]).toInt(); /* requested position to seek to (in seconds) */ double offset = double.parse("${event["offset"]}"); diff --git a/lib/textTrack.dart b/lib/textTrack.dart index 5584fac..33f23e9 100644 --- a/lib/textTrack.dart +++ b/lib/textTrack.dart @@ -1,5 +1,3 @@ -import 'package:flutter/cupertino.dart'; - /// Used for adding text tracks to the player. [mimetype] is the type of text /// track for example text/webvtt. [languageCode] is the language of the track /// for example `en` or `fr` and [uri] is the location for the track. @@ -11,9 +9,9 @@ class TextTrack { TextTrack(this.mimetype, this.languageCode, this.uri); factory TextTrack.from( - {@required String mimetype, - @required String languageCode, - @required String uri}) { + {required String mimetype, + required String languageCode, + required String uri}) { return new TextTrack(mimetype, languageCode, uri); } diff --git a/lib/video.dart b/lib/video.dart index db75e93..7ef8ad9 100644 --- a/lib/video.dart +++ b/lib/video.dart @@ -28,19 +28,19 @@ class Video extends StatefulWidget { final bool autoPlay; final bool loop; final bool showControls; - final String url; - final String title; - final String subtitle; - final String preferredAudioLanguage; - final List textTracks; - final String preferredTextLanguage; + final String? url; + final String? title; + final String? subtitle; + final String? preferredAudioLanguage; + final List? textTracks; + final String? preferredTextLanguage; final bool isLiveStream; final double position; - final Function onViewCreated; + final Function? onViewCreated; final PlayerState desiredState; const Video( - {Key key, + {Key? key, this.autoPlay = false, this.loop = false, this.showControls = true, @@ -61,8 +61,8 @@ class Video extends StatefulWidget { } class _VideoState extends State