diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 08a5e443149e..4a33d2e65ce5 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.3 + +* Add macOS support. + ## 2.0.2 * Fix `VideoPlayerValue` size and aspect ratio documentation diff --git a/packages/video_player/video_player/example/integration_test/video_player_test.dart b/packages/video_player/video_player/example/integration_test/video_player_test.dart index 9e273e02dc4d..ed7f75dae605 100644 --- a/packages/video_player/video_player/example/integration_test/video_player_test.dart +++ b/packages/video_player/video_player/example/integration_test/video_player_test.dart @@ -20,9 +20,11 @@ void main() { VideoPlayerController _controller; tearDown(() async => _controller.dispose()); - group('asset videos', () { + group('network videos', () { setUp(() { - _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); + _controller = VideoPlayerController.network( + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', + ); }); testWidgets('can be initialized', (WidgetTester tester) async { @@ -32,81 +34,30 @@ void main() { expect(_controller.value.position, const Duration(seconds: 0)); expect(_controller.value.isPlaying, false); expect(_controller.value.duration, - const Duration(seconds: 7, milliseconds: 540)); + const Duration(seconds: 4, milliseconds: 036)); }); - testWidgets( - 'reports buffering status', - (WidgetTester tester) async { - VideoPlayerController networkController = VideoPlayerController.network( - 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', - ); - await networkController.initialize(); - // Mute to allow playing without DOM interaction on Web. - // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes - await networkController.setVolume(0); - final Completer started = Completer(); - final Completer ended = Completer(); - bool startedBuffering = false; - bool endedBuffering = false; - networkController.addListener(() { - if (networkController.value.isBuffering && !startedBuffering) { - startedBuffering = true; - started.complete(); - } - if (startedBuffering && - !networkController.value.isBuffering && - !endedBuffering) { - endedBuffering = true; - ended.complete(); - } - }); - - await networkController.play(); - await networkController.seekTo(const Duration(seconds: 5)); - await tester.pumpAndSettle(_playDuration); - await networkController.pause(); - - expect(networkController.value.isPlaying, false); - expect(networkController.value.position, - (Duration position) => position > const Duration(seconds: 0)); - - await started; - expect(startedBuffering, true); + testWidgets('can be played', (WidgetTester tester) async { + await _controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await _controller.setVolume(0); - await ended; - expect(endedBuffering, true); - }, - skip: !(kIsWeb || defaultTargetPlatform == TargetPlatform.android), - ); + await _controller.play(); + await tester.pumpAndSettle(_playDuration); - testWidgets( - 'can be played', - (WidgetTester tester) async { - await _controller.initialize(); - // Mute to allow playing without DOM interaction on Web. - // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes - await _controller.setVolume(0); - - await _controller.play(); - await tester.pumpAndSettle(_playDuration); - - expect(_controller.value.isPlaying, true); - expect(_controller.value.position, - (Duration position) => position > const Duration(seconds: 0)); - }, - ); + expect(_controller.value.isPlaying, true); + expect(_controller.value.position, + (Duration position) => position > const Duration(seconds: 0)); + }); - testWidgets( - 'can seek', - (WidgetTester tester) async { - await _controller.initialize(); + testWidgets('can seek', (WidgetTester tester) async { + await _controller.initialize(); - await _controller.seekTo(const Duration(seconds: 3)); + await _controller.seekTo(const Duration(seconds: 3)); - expect(_controller.value.position, const Duration(seconds: 3)); - }, - ); + expect(_controller.value.position, const Duration(seconds: 3)); + }); testWidgets( 'can be paused', @@ -129,8 +80,136 @@ void main() { }, ); - testWidgets('test video player view with local asset', - (WidgetTester tester) async { + testWidgets('reports buffering status', (WidgetTester tester) async { + await _controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await _controller.setVolume(0); + final Completer started = Completer(); + final Completer ended = Completer(); + bool startedBuffering = false; + bool endedBuffering = false; + _controller.addListener( + () { + if (_controller.value.isBuffering && !startedBuffering) { + startedBuffering = true; + started.complete(); + } + if (startedBuffering && + !_controller.value.isBuffering && + !endedBuffering) { + endedBuffering = true; + ended.complete(); + } + }, + ); + + await _controller.play(); + await _controller.seekTo(const Duration(seconds: 5)); + await tester.pumpAndSettle(_playDuration); + await _controller.pause(); + + expect(_controller.value.isPlaying, false); + expect(_controller.value.position, + (Duration position) => position > const Duration(seconds: 0)); + + await started; + expect(startedBuffering, true); + + await ended; + expect(endedBuffering, true); + }); + + testWidgets('can show video player', (WidgetTester tester) async { + Future started() async { + await _controller.initialize(); + await _controller.play(); + return true; + } + + await tester.pumpWidget(Material( + elevation: 0, + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: FutureBuilder( + future: started(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.data == true) { + return AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: VideoPlayer(_controller), + ); + } else { + return const Text('waiting for video to load'); + } + }, + ), + ), + ), + )); + + await tester.pumpAndSettle(_playDuration); + expect(_controller.value.isPlaying, true); + }); + }); + + group('asset videos', () { + setUp(() { + _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); + }); + + testWidgets('can be initialized', (WidgetTester tester) async { + await _controller.initialize(); + + expect(_controller.value.isInitialized, true); + expect(_controller.value.position, const Duration(seconds: 0)); + expect(_controller.value.isPlaying, false); + expect(_controller.value.duration, + const Duration(seconds: 7, milliseconds: 540)); + }, skip: (kIsWeb || defaultTargetPlatform == TargetPlatform.macOS)); + + testWidgets('can be played', (WidgetTester tester) async { + await _controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await _controller.setVolume(0); + + await _controller.play(); + await tester.pumpAndSettle(_playDuration); + + expect(_controller.value.isPlaying, true); + expect(_controller.value.position, + (Duration position) => position > const Duration(seconds: 0)); + }, skip: (kIsWeb || defaultTargetPlatform == TargetPlatform.macOS)); + + testWidgets('can seek', (WidgetTester tester) async { + await _controller.initialize(); + + await _controller.seekTo(const Duration(seconds: 3)); + + expect(_controller.value.position, const Duration(seconds: 3)); + }, skip: (kIsWeb || defaultTargetPlatform == TargetPlatform.macOS)); + + testWidgets('can be paused', (WidgetTester tester) async { + await _controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await _controller.setVolume(0); + + // Play for a second, then pause, and then wait a second. + await _controller.play(); + await tester.pumpAndSettle(_playDuration); + await _controller.pause(); + final Duration pausedPosition = _controller.value.position; + await tester.pumpAndSettle(_playDuration); + + // Verify that we stopped playing after the pause. + expect(_controller.value.isPlaying, false); + expect(_controller.value.position, pausedPosition); + }, skip: (kIsWeb || defaultTargetPlatform == TargetPlatform.macOS)); + + testWidgets('can show video player', (WidgetTester tester) async { Future started() async { await _controller.initialize(); await _controller.play(); @@ -159,8 +238,8 @@ void main() { ), )); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(_playDuration); expect(_controller.value.isPlaying, true); - }, skip: kIsWeb); // Web does not support local assets. + }, skip: (kIsWeb || defaultTargetPlatform == TargetPlatform.macOS)); }); } diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 17442d7ec09a..30ce2635d12b 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -1,7 +1,7 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, and web. -version: 2.0.2 +version: 2.0.3 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player flutter: diff --git a/packages/video_player/video_player_macos/CHANGELOG.md b/packages/video_player/video_player_macos/CHANGELOG.md new file mode 100644 index 000000000000..d0bd041d0ff6 --- /dev/null +++ b/packages/video_player/video_player_macos/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* Initial release. diff --git a/packages/video_player/video_player_macos/LICENSE b/packages/video_player/video_player_macos/LICENSE new file mode 100644 index 000000000000..ba75c69f7f21 --- /dev/null +++ b/packages/video_player/video_player_macos/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/video_player/video_player_macos/README.md b/packages/video_player/video_player_macos/README.md new file mode 100644 index 000000000000..dece5aab21ff --- /dev/null +++ b/packages/video_player/video_player_macos/README.md @@ -0,0 +1,34 @@ +# video_player_macos + +The macos implementation of [`video_player`][1]. + +## Usage + +### Import the package + +This package has been endorsed, meaning that you only need to add `video_player` +as a dependency in your `pubspec.yaml`. It will be automatically included in your app +when you depend on `package:video_player`. + +This is what the above means to your `pubspec.yaml`: + +```yaml +... +dependencies: + ... + video_player: ^2.0.3 + ... +``` + +If you wish to use the macos package only, you can add `video_player_macos` as a +dependency: + +```yaml +... +dependencies: + ... + video_player_macos: ^0.0.1 + ... +``` + +[1]: ../video_player/video_player diff --git a/packages/video_player/video_player_macos/example/.gitignore b/packages/video_player/video_player_macos/example/.gitignore new file mode 100644 index 000000000000..d3e68fd01e5d --- /dev/null +++ b/packages/video_player/video_player_macos/example/.gitignore @@ -0,0 +1 @@ +lib/generated_plugin_registrant.dart diff --git a/packages/video_player/video_player_macos/example/.metadata b/packages/video_player/video_player_macos/example/.metadata new file mode 100644 index 000000000000..c69346d9d323 --- /dev/null +++ b/packages/video_player/video_player_macos/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: c5a4b4029c0798f37c4a39b479d7cb75daa7b05c + channel: beta + +project_type: app diff --git a/packages/video_player/video_player_macos/example/README.md b/packages/video_player/video_player_macos/example/README.md new file mode 100644 index 000000000000..8ceb0ff485fa --- /dev/null +++ b/packages/video_player/video_player_macos/example/README.md @@ -0,0 +1,8 @@ +# video_player_example + +Demonstrates how to use the video_player plugin. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](https://flutter.dev/). diff --git a/packages/video_player/video_player_macos/example/assets/bumble_bee_captions.srt b/packages/video_player/video_player_macos/example/assets/bumble_bee_captions.srt new file mode 100644 index 000000000000..59d749a2082b --- /dev/null +++ b/packages/video_player/video_player_macos/example/assets/bumble_bee_captions.srt @@ -0,0 +1,7 @@ +1 +00:00:00,200 --> 00:00:01,750 +[ Birds chirping ] + +2 +00:00:02,300 --> 00:00:05,000 +[ Buzzing ] diff --git a/packages/video_player/video_player_macos/example/integration_test/video_player_test.dart b/packages/video_player/video_player_macos/example/integration_test/video_player_test.dart new file mode 100644 index 000000000000..aa09ed080cf5 --- /dev/null +++ b/packages/video_player/video_player_macos/example/integration_test/video_player_test.dart @@ -0,0 +1,144 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// TODO(amirh): Remove this once flutter_driver supports null safety. +// https://github.com/flutter/flutter/issues/71379 +// @dart = 2.9 +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:video_player/video_player.dart'; + +const Duration _playDuration = Duration(seconds: 1); + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + VideoPlayerController _controller; + tearDown(() async => _controller.dispose()); + + group('network videos', () { + setUp(() { + _controller = VideoPlayerController.network( + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', + ); + }); + + testWidgets('can be initialized', (WidgetTester tester) async { + await _controller.initialize(); + + expect(_controller.value.isInitialized, true); + expect(_controller.value.position, const Duration(seconds: 0)); + expect(_controller.value.isPlaying, false); + expect(_controller.value.duration, + const Duration(seconds: 4, milliseconds: 036)); + }); + + testWidgets('can be played', (WidgetTester tester) async { + await _controller.initialize(); + await _controller.play(); + await tester.pumpAndSettle(_playDuration); + + expect(_controller.value.isPlaying, true); + expect(_controller.value.position, + (Duration position) => position > const Duration(seconds: 0)); + }); + + testWidgets('can seek', (WidgetTester tester) async { + await _controller.initialize(); + + await _controller.seekTo(const Duration(seconds: 3)); + + expect(_controller.value.position, const Duration(seconds: 3)); + }); + + testWidgets( + 'can be paused', + (WidgetTester tester) async { + await _controller.initialize(); + // Play for a second, then pause, and then wait a second. + await _controller.play(); + await tester.pumpAndSettle(_playDuration); + await _controller.pause(); + final Duration pausedPosition = _controller.value.position; + await tester.pumpAndSettle(_playDuration); + + // Verify that we stopped playing after the pause. + expect(_controller.value.isPlaying, false); + expect(_controller.value.position, pausedPosition); + }, + ); + + testWidgets('reports buffering status', (WidgetTester tester) async { + await _controller.initialize(); + final Completer started = Completer(); + final Completer ended = Completer(); + bool startedBuffering = false; + bool endedBuffering = false; + _controller.addListener( + () { + if (_controller.value.isBuffering && !startedBuffering) { + startedBuffering = true; + started.complete(); + } + if (startedBuffering && + !_controller.value.isBuffering && + !endedBuffering) { + endedBuffering = true; + ended.complete(); + } + }, + ); + + await _controller.play(); + await _controller.seekTo(const Duration(seconds: 5)); + await tester.pumpAndSettle(_playDuration); + await _controller.pause(); + + expect(_controller.value.isPlaying, false); + expect(_controller.value.position, + (Duration position) => position > const Duration(seconds: 0)); + + await started; + expect(startedBuffering, true); + + await ended; + expect(endedBuffering, true); + }); + + testWidgets('can show video player', (WidgetTester tester) async { + Future started() async { + await _controller.initialize(); + await _controller.play(); + return true; + } + + await tester.pumpWidget(Material( + elevation: 0, + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: FutureBuilder( + future: started(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.data == true) { + return AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: VideoPlayer(_controller), + ); + } else { + return const Text('waiting for video to load'); + } + }, + ), + ), + ), + )); + + await tester.pumpAndSettle(_playDuration); + expect(_controller.value.isPlaying, true); + }); + }); +} diff --git a/packages/video_player/video_player_macos/example/lib/main.dart b/packages/video_player/video_player_macos/example/lib/main.dart new file mode 100644 index 000000000000..5491020c4bfd --- /dev/null +++ b/packages/video_player/video_player_macos/example/lib/main.dart @@ -0,0 +1,229 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +/// An example of using the plugin, controlling lifecycle and playback of the +/// video. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:video_player/video_player.dart'; + +void main() { + runApp( + MaterialApp( + home: _App(), + ), + ); +} + +class _App extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + key: const ValueKey('home_page'), + appBar: AppBar(title: const Text('Video player example')), + body: _BumbleBeeRemoteVideo(), + ); + } +} + +class _BumbleBeeRemoteVideo extends StatefulWidget { + @override + _BumbleBeeRemoteVideoState createState() => _BumbleBeeRemoteVideoState(); +} + +class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { + late VideoPlayerController _controller; + + Future _loadCaptions() async { + final String fileContents = await DefaultAssetBundle.of(context) + .loadString('assets/bumble_bee_captions.srt'); + return SubRipCaptionFile(fileContents); + } + + @override + void initState() { + super.initState(); + _controller = VideoPlayerController.network( + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', + closedCaptionFile: _loadCaptions(), + videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), + ); + + _controller.addListener(() { + setState(() {}); + }); + _controller.setLooping(true); + _controller.initialize(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + Container(padding: const EdgeInsets.only(top: 20.0)), + const Text('With remote mp4'), + Container( + padding: const EdgeInsets.all(20), + child: AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + VideoPlayer(_controller), + ClosedCaption(text: _controller.value.caption.text), + _ControlsOverlay(controller: _controller), + VideoProgressIndicator(_controller, allowScrubbing: true), + ], + ), + ), + ), + ], + ), + ); + } +} + +class _ControlsOverlay extends StatelessWidget { + const _ControlsOverlay({Key? key, required this.controller}) + : super(key: key); + + static const _examplePlaybackRates = [ + 0.25, + 0.5, + 1.0, + 1.5, + 2.0, + 3.0, + 5.0, + 10.0, + ]; + + final VideoPlayerController controller; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + AnimatedSwitcher( + duration: Duration(milliseconds: 50), + reverseDuration: Duration(milliseconds: 200), + child: controller.value.isPlaying + ? SizedBox.shrink() + : Container( + color: Colors.black26, + child: Center( + child: Icon( + Icons.play_arrow, + color: Colors.white, + size: 100.0, + ), + ), + ), + ), + GestureDetector( + onTap: () { + controller.value.isPlaying ? controller.pause() : controller.play(); + }, + ), + Align( + alignment: Alignment.topRight, + child: PopupMenuButton( + initialValue: controller.value.playbackSpeed, + tooltip: 'Playback speed', + onSelected: (speed) { + controller.setPlaybackSpeed(speed); + }, + itemBuilder: (context) { + return [ + for (final speed in _examplePlaybackRates) + PopupMenuItem( + value: speed, + child: Text('${speed}x'), + ) + ]; + }, + child: Padding( + padding: const EdgeInsets.symmetric( + // Using less vertical padding as the text is also longer + // horizontally, so it feels like it would need more spacing + // horizontally (matching the aspect ratio of the video). + vertical: 12, + horizontal: 16, + ), + child: Text('${controller.value.playbackSpeed}x'), + ), + ), + ), + ], + ); + } +} + +class _PlayerVideoAndPopPage extends StatefulWidget { + @override + _PlayerVideoAndPopPageState createState() => _PlayerVideoAndPopPageState(); +} + +class _PlayerVideoAndPopPageState extends State<_PlayerVideoAndPopPage> { + late VideoPlayerController _videoPlayerController; + bool startedPlaying = false; + + @override + void initState() { + super.initState(); + + _videoPlayerController = + VideoPlayerController.asset('assets/Butterfly-209.mp4'); + _videoPlayerController.addListener(() { + if (startedPlaying && !_videoPlayerController.value.isPlaying) { + Navigator.pop(context); + } + }); + } + + @override + void dispose() { + _videoPlayerController.dispose(); + super.dispose(); + } + + Future started() async { + await _videoPlayerController.initialize(); + await _videoPlayerController.play(); + startedPlaying = true; + return true; + } + + @override + Widget build(BuildContext context) { + return Material( + elevation: 0, + child: Center( + child: FutureBuilder( + future: started(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.data == true) { + return AspectRatio( + aspectRatio: _videoPlayerController.value.aspectRatio, + child: VideoPlayer(_videoPlayerController), + ); + } else { + return const Text('waiting for video to load'); + } + }, + ), + ), + ); + } +} diff --git a/packages/video_player/video_player_macos/example/macos/.gitignore b/packages/video_player/video_player_macos/example/macos/.gitignore new file mode 100644 index 000000000000..d2fd3772308c --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/.gitignore @@ -0,0 +1,6 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/xcuserdata/ diff --git a/packages/video_player/video_player_macos/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/video_player/video_player_macos/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 000000000000..4b81f9b2d200 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/video_player/video_player_macos/example/macos/Flutter/Flutter-Release.xcconfig b/packages/video_player/video_player_macos/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 000000000000..5caa9d1579e4 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/video_player/video_player_macos/example/macos/Runner.xcodeproj/project.pbxproj b/packages/video_player/video_player_macos/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..978c36c22235 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,632 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 46F7EC0E0D3C803AAE47E512 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF376A98B1F9823E6A72941A /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0E09D96D2BB862996435C4B4 /* 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 = ""; }; + 28542430F9D5D1A7548FCFB9 /* 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 = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5C322878584E81F087AF4F11 /* 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 = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + AF376A98B1F9823E6A72941A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 46F7EC0E0D3C803AAE47E512 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2BA593EA198E189C7628B6C8 /* Pods */ = { + isa = PBXGroup; + children = ( + 28542430F9D5D1A7548FCFB9 /* Pods-Runner.debug.xcconfig */, + 5C322878584E81F087AF4F11 /* Pods-Runner.release.xcconfig */, + 0E09D96D2BB862996435C4B4 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 2BA593EA198E189C7628B6C8 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + AF376A98B1F9823E6A72941A /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 8D4BD3092387DF89F1A443AB /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 0CE5A8C6BB8C8B05F4C4FCCC /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 0930; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0CE5A8C6BB8C8B05F4C4FCCC /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 8D4BD3092387DF89F1A443AB /* [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; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/video_player/video_player_macos/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/video_player/video_player_macos/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/video_player/video_player_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/video_player/video_player_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000000..ae8ff59d97b3 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/video_player/video_player_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/video_player/video_player_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..21a3cc14c74e --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/video_player/video_player_macos/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/video_player/video_player_macos/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/video_player/video_player_macos/example/macos/Runner/AppDelegate.swift b/packages/video_player/video_player_macos/example/macos/Runner/AppDelegate.swift new file mode 100644 index 000000000000..d53ef6437726 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000000..a2ec33f19f11 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 000000000000..3c4935a7ca84 Binary files /dev/null and b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 000000000000..ed4cc1642168 Binary files /dev/null and b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 000000000000..483be6138973 Binary files /dev/null and b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 000000000000..bcbf36df2f2a Binary files /dev/null and b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 000000000000..9c0a65286476 Binary files /dev/null and b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 000000000000..e71a726136a4 Binary files /dev/null and b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 000000000000..8a31fe2dd3f9 Binary files /dev/null and b/packages/video_player/video_player_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/video_player/video_player_macos/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 000000000000..537341abf994 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,339 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/video_player/video_player_macos/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 000000000000..cccda2e8a262 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2021 io.flutter.plugins. All rights reserved. diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Configs/Debug.xcconfig b/packages/video_player/video_player_macos/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 000000000000..36b0fd9464f4 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Configs/Release.xcconfig b/packages/video_player/video_player_macos/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 000000000000..dff4f49561c8 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Configs/Warnings.xcconfig b/packages/video_player/video_player_macos/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 000000000000..42bcbf4780b1 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/video_player/video_player_macos/example/macos/Runner/DebugProfile.entitlements b/packages/video_player/video_player_macos/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 000000000000..3ba6c1266f21 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Info.plist b/packages/video_player/video_player_macos/example/macos/Runner/Info.plist new file mode 100644 index 000000000000..4789daa6a443 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/video_player/video_player_macos/example/macos/Runner/MainFlutterWindow.swift b/packages/video_player/video_player_macos/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 000000000000..2722837ec918 --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/video_player/video_player_macos/example/macos/Runner/Release.entitlements b/packages/video_player/video_player_macos/example/macos/Runner/Release.entitlements new file mode 100644 index 000000000000..7a2230dc331d --- /dev/null +++ b/packages/video_player/video_player_macos/example/macos/Runner/Release.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/packages/video_player/video_player_macos/example/pubspec.yaml b/packages/video_player/video_player_macos/example/pubspec.yaml new file mode 100644 index 000000000000..952241a78172 --- /dev/null +++ b/packages/video_player/video_player_macos/example/pubspec.yaml @@ -0,0 +1,36 @@ +name: video_player_macos_example +description: Demonstrates how to use the video_player plugin. +version: 0.0.1 +publish_to: none + +dependencies: + flutter: + sdk: flutter + video_player: + path: ../../video_player + video_player_macos: + # When depending on this package from a real application you should use: + # video_player_macos: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_driver: + sdk: flutter + integration_test: + path: ../../../integration_test + test: any + pedantic: ^1.10.0 + +flutter: + uses-material-design: true + assets: + - assets/bumble_bee_captions.srt + +environment: + sdk: ">=2.12.0-259.9.beta <3.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/video_player/video_player_macos/example/test_driver/integration_test.dart b/packages/video_player/video_player_macos/example/test_driver/integration_test.dart new file mode 100644 index 000000000000..7873abae2996 --- /dev/null +++ b/packages/video_player/video_player_macos/example/test_driver/integration_test.dart @@ -0,0 +1,20 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// TODO(egarciad): Remove once Flutter driver is migrated to null safety. +// @dart = 2.9 + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + final String data = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + await driver.close(); + final Map result = jsonDecode(data); + exit(result['result'] == 'true' ? 0 : 1); +} diff --git a/packages/video_player/video_player_macos/example/test_driver/video_player.dart b/packages/video_player/video_player_macos/example/test_driver/video_player.dart new file mode 100644 index 000000000000..0c228db9ae85 --- /dev/null +++ b/packages/video_player/video_player_macos/example/test_driver/video_player.dart @@ -0,0 +1,14 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// TODO(egarciad): Remove once Flutter driver is migrated to null safety. +// @dart = 2.9 + +import 'package:flutter_driver/driver_extension.dart'; +import 'package:video_player_macos_example/main.dart' as app; + +void main() { + enableFlutterDriverExtension(); + app.main(); +} diff --git a/packages/video_player/video_player_macos/lib/video_player_macos.dart b/packages/video_player/video_player_macos/lib/video_player_macos.dart new file mode 100644 index 000000000000..2553d61f4292 --- /dev/null +++ b/packages/video_player/video_player_macos/lib/video_player_macos.dart @@ -0,0 +1,3 @@ +// The video_player_platform_interface defaults to MethodChannelVideoPlayer +// as its instance, which is all the macOS implementation needs. This file +// is here to silence warnings when publishing to pub. \ No newline at end of file diff --git a/packages/video_player/video_player_macos/macos/Assets/.gitkeep b/packages/video_player/video_player_macos/macos/Assets/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/video_player/video_player_macos/macos/Classes/FLTVideoPlayerPlugin.h b/packages/video_player/video_player_macos/macos/Classes/FLTVideoPlayerPlugin.h new file mode 100644 index 000000000000..7c41b4ae8650 --- /dev/null +++ b/packages/video_player/video_player_macos/macos/Classes/FLTVideoPlayerPlugin.h @@ -0,0 +1,8 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +@interface FLTVideoPlayerPlugin : NSObject +@end diff --git a/packages/video_player/video_player_macos/macos/Classes/FLTVideoPlayerPlugin.m b/packages/video_player/video_player_macos/macos/Classes/FLTVideoPlayerPlugin.m new file mode 100644 index 000000000000..5d0de88c6f5c --- /dev/null +++ b/packages/video_player/video_player_macos/macos/Classes/FLTVideoPlayerPlugin.m @@ -0,0 +1,631 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "FLTVideoPlayerPlugin.h" +#import +#import +#import "messages.h" + +#if !__has_feature(objc_arc) +#error Code Requires ARC. +#endif + +int64_t FLTCMTimeToMillis(CMTime time) { + if (time.timescale == 0) return 0; + return time.value * 1000 / time.timescale; +} + +@interface FLTFrameUpdater : NSObject +@property(nonatomic) int64_t textureId; +@property(nonatomic, weak, readonly) NSObject* registry; +- (void)notifyFrameAvailable; +@end + +@implementation FLTFrameUpdater +- (FLTFrameUpdater*)initWithRegistry:(NSObject*)registry { + NSAssert(self, @"super init cannot be nil"); + if (self == nil) return nil; + _registry = registry; + return self; +} + +- (void)notifyFrameAvailable { + [_registry textureFrameAvailable:_textureId]; +} +@end + +@interface FLTVideoPlayer : NSObject +@property(readonly, nonatomic) AVPlayer* player; +@property(readonly, nonatomic) AVPlayerItem* playerItem; +@property(readonly, nonatomic) AVPlayerItemVideoOutput* videoOutput; +@property(readonly, nonatomic) CVDisplayLinkRef displayLink; +@property(readonly, nonatomic) FLTFrameUpdater* frameUpdater; +@property(nonatomic) FlutterEventChannel* eventChannel; +@property(nonatomic) FlutterEventSink eventSink; +@property(nonatomic) CGAffineTransform preferredTransform; +@property(nonatomic, readonly) bool disposed; +@property(nonatomic, readonly) bool isPlaying; +@property(nonatomic) bool isLooping; +@property(nonatomic, readonly) bool isInitialized; +- (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpdater; +- (void)play; +- (void)pause; +- (void)setIsLooping:(bool)isLooping; +- (void)updatePlayingState; +@end + +static void* timeRangeContext = &timeRangeContext; +static void* statusContext = &statusContext; +static void* playbackLikelyToKeepUpContext = &playbackLikelyToKeepUpContext; +static void* playbackBufferEmptyContext = &playbackBufferEmptyContext; +static void* playbackBufferFullContext = &playbackBufferFullContext; + +@implementation FLTVideoPlayer +- (instancetype)initWithAsset:(NSString*)asset frameUpdater:(FLTFrameUpdater*)frameUpdater { + NSString* path = [[NSBundle mainBundle] pathForResource:asset ofType:nil]; + return [self initWithURL:[NSURL fileURLWithPath:path] frameUpdater:frameUpdater]; +} + +- (void)addObservers:(AVPlayerItem*)item { + [item addObserver:self + forKeyPath:@"loadedTimeRanges" + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:timeRangeContext]; + [item addObserver:self + forKeyPath:@"status" + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:statusContext]; + [item addObserver:self + forKeyPath:@"playbackLikelyToKeepUp" + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:playbackLikelyToKeepUpContext]; + [item addObserver:self + forKeyPath:@"playbackBufferEmpty" + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:playbackBufferEmptyContext]; + [item addObserver:self + forKeyPath:@"playbackBufferFull" + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:playbackBufferFullContext]; + + // Add an observer that will respond to itemDidPlayToEndTime + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(itemDidPlayToEndTime:) + name:AVPlayerItemDidPlayToEndTimeNotification + object:item]; +} + +- (void)itemDidPlayToEndTime:(NSNotification*)notification { + if (_isLooping) { + AVPlayerItem* p = [notification object]; + [p seekToTime:kCMTimeZero completionHandler:nil]; + } else { + if (_eventSink) { + _eventSink(@{@"event" : @"completed"}); + } + } +} + +static inline CGFloat radiansToDegrees(CGFloat radians) { + // Input range [-pi, pi] or [-180, 180] + CGFloat degrees = GLKMathRadiansToDegrees((float)radians); + if (degrees < 0) { + // Convert -90 to 270 and -180 to 180 + return degrees + 360; + } + // Output degrees in between [0, 360[ + return degrees; +}; + +- (AVMutableVideoComposition*)getVideoCompositionWithTransform:(CGAffineTransform)transform + withAsset:(AVAsset*)asset + withVideoTrack:(AVAssetTrack*)videoTrack { + AVMutableVideoCompositionInstruction* instruction = + [AVMutableVideoCompositionInstruction videoCompositionInstruction]; + instruction.timeRange = CMTimeRangeMake(kCMTimeZero, [asset duration]); + AVMutableVideoCompositionLayerInstruction* layerInstruction = + [AVMutableVideoCompositionLayerInstruction + videoCompositionLayerInstructionWithAssetTrack:videoTrack]; + [layerInstruction setTransform:_preferredTransform atTime:kCMTimeZero]; + + AVMutableVideoComposition* videoComposition = [AVMutableVideoComposition videoComposition]; + instruction.layerInstructions = @[ layerInstruction ]; + videoComposition.instructions = @[ instruction ]; + + // If in portrait mode, switch the width and height of the video + CGFloat width = videoTrack.naturalSize.width; + CGFloat height = videoTrack.naturalSize.height; + NSInteger rotationDegrees = + (NSInteger)round(radiansToDegrees(atan2(_preferredTransform.b, _preferredTransform.a))); + if (rotationDegrees == 90 || rotationDegrees == 270) { + width = videoTrack.naturalSize.height; + height = videoTrack.naturalSize.width; + } + videoComposition.renderSize = CGSizeMake(width, height); + + // TODO(@recastrodiaz): should we use videoTrack.nominalFrameRate ? + // Currently set at a constant 30 FPS + videoComposition.frameDuration = CMTimeMake(1, 30); + + return videoComposition; +} + +- (void)createVideoOutputAndDisplayLink:(FLTFrameUpdater*)frameUpdater { + NSDictionary* pixBuffAttributes = @{ + (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA), + (id)kCVPixelBufferIOSurfacePropertiesKey : @{}, + (id)kCVPixelBufferOpenGLCompatibilityKey : @YES, + (id)kCVPixelBufferMetalCompatibilityKey : @YES, + }; + _videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes]; + + [self startDisplayLink]; + CVDisplayLinkStop(_displayLink); +} + +- (void)notifyFrameAvailable { + if (!_playerItem || _playerItem.status != AVPlayerItemStatusReadyToPlay) { + return; + } + [_frameUpdater notifyFrameAvailable]; +} + +static CVReturn OnDisplayLink(CVDisplayLinkRef CV_NONNULL displayLink, + const CVTimeStamp* CV_NONNULL inNow, + const CVTimeStamp* CV_NONNULL inOutputTime, CVOptionFlags flagsIn, + CVOptionFlags* CV_NONNULL flagsOut, + void* CV_NULLABLE displayLinkContext) { + __weak FLTVideoPlayer* video_player = (__bridge FLTVideoPlayer*)(displayLinkContext); + dispatch_async(dispatch_get_main_queue(), ^{ + [video_player notifyFrameAvailable]; + }); + return kCVReturnSuccess; +} + +- (void)startDisplayLink { + if (_displayLink != NULL) { + return; + } + + if (CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), &_displayLink) != kCVReturnSuccess) { + _displayLink = NULL; + return; + } + + CVDisplayLinkSetOutputCallback(_displayLink, OnDisplayLink, (__bridge void*)self); + CVDisplayLinkStart(_displayLink); +} + +- (void)stopDisplayLink { + if (_displayLink == NULL) { + return; + } + + CVDisplayLinkStop(_displayLink); + CVDisplayLinkRelease(_displayLink); +} + +- (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpdater { + _playerItem = [AVPlayerItem playerItemWithURL:url]; + _frameUpdater = frameUpdater; + return [self initWithPlayerItem:_playerItem frameUpdater:frameUpdater]; +} + +- (CGAffineTransform)fixTransform:(AVAssetTrack*)videoTrack { + CGAffineTransform transform = videoTrack.preferredTransform; + // TODO(@recastrodiaz): why do we need to do this? Why is the preferredTransform incorrect? + // At least 2 user videos show a black screen when in portrait mode if we directly use the + // videoTrack.preferredTransform Setting tx to the height of the video instead of 0, properly + // displays the video https://github.com/flutter/flutter/issues/17606#issuecomment-413473181 + if (transform.tx == 0 && transform.ty == 0) { + NSInteger rotationDegrees = (NSInteger)round(radiansToDegrees(atan2(transform.b, transform.a))); + NSLog(@"TX and TY are 0. Rotation: %ld. Natural width,height: %f, %f", (long)rotationDegrees, + videoTrack.naturalSize.width, videoTrack.naturalSize.height); + if (rotationDegrees == 90) { + NSLog(@"Setting transform tx"); + transform.tx = videoTrack.naturalSize.height; + transform.ty = 0; + } else if (rotationDegrees == 270) { + NSLog(@"Setting transform ty"); + transform.tx = 0; + transform.ty = videoTrack.naturalSize.width; + } + } + return transform; +} + +- (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpdater*)frameUpdater { + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _isInitialized = false; + _isPlaying = false; + _disposed = false; + + AVAsset* asset = [item asset]; + void (^assetCompletionHandler)(void) = ^{ + if ([asset statusOfValueForKey:@"tracks" error:nil] == AVKeyValueStatusLoaded) { + NSArray* tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; + if ([tracks count] > 0) { + AVAssetTrack* videoTrack = tracks[0]; + void (^trackCompletionHandler)(void) = ^{ + if (self->_disposed) return; + if ([videoTrack statusOfValueForKey:@"preferredTransform" + error:nil] == AVKeyValueStatusLoaded) { + // Rotate the video by using a videoComposition and the preferredTransform + self->_preferredTransform = [self fixTransform:videoTrack]; + // Note: + // https://developer.apple.com/documentation/avfoundation/avplayeritem/1388818-videocomposition + // Video composition can only be used with file-based media and is not supported for + // use with media served using HTTP Live Streaming. + AVMutableVideoComposition* videoComposition = + [self getVideoCompositionWithTransform:self->_preferredTransform + withAsset:asset + withVideoTrack:videoTrack]; + item.videoComposition = videoComposition; + } + }; + [videoTrack loadValuesAsynchronouslyForKeys:@[ @"preferredTransform" ] + completionHandler:trackCompletionHandler]; + } + } + }; + + _player = [AVPlayer playerWithPlayerItem:item]; + _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; + + [self createVideoOutputAndDisplayLink:frameUpdater]; + + [self addObservers:item]; + + [asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ] completionHandler:assetCompletionHandler]; + + return self; +} + +- (void)observeValueForKeyPath:(NSString*)path + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context { + if (context == timeRangeContext) { + if (_eventSink != nil) { + NSMutableArray*>* values = [[NSMutableArray alloc] init]; + for (NSValue* rangeValue in [object loadedTimeRanges]) { + CMTimeRange range = [rangeValue CMTimeRangeValue]; + int64_t start = FLTCMTimeToMillis(range.start); + [values addObject:@[ @(start), @(start + FLTCMTimeToMillis(range.duration)) ]]; + } + _eventSink(@{@"event" : @"bufferingUpdate", @"values" : values}); + } + } else if (context == statusContext) { + AVPlayerItem* item = (AVPlayerItem*)object; + switch (item.status) { + case AVPlayerItemStatusFailed: + if (_eventSink != nil) { + _eventSink([FlutterError + errorWithCode:@"VideoError" + message:[@"Failed to load video: " + stringByAppendingString:[item.error localizedDescription]] + details:nil]); + } + break; + case AVPlayerItemStatusUnknown: + break; + case AVPlayerItemStatusReadyToPlay: + [item addOutput:_videoOutput]; + [self sendInitialized]; + [self updatePlayingState]; + break; + } + } else if (context == playbackLikelyToKeepUpContext) { + if ([[_player currentItem] isPlaybackLikelyToKeepUp]) { + [self updatePlayingState]; + if (_eventSink != nil) { + _eventSink(@{@"event" : @"bufferingEnd"}); + } + } + } else if (context == playbackBufferEmptyContext) { + if (_eventSink != nil) { + _eventSink(@{@"event" : @"bufferingStart"}); + } + } else if (context == playbackBufferFullContext) { + if (_eventSink != nil) { + _eventSink(@{@"event" : @"bufferingEnd"}); + } + } +} + +- (void)updatePlayingState { + if (!_isInitialized) { + return; + } + if (_isPlaying) { + [_player play]; + CVDisplayLinkStart(_displayLink); + } else { + [_player pause]; + CVDisplayLinkStop(_displayLink); + } +} + +- (void)sendInitialized { + if (_eventSink && !_isInitialized) { + CGSize size = [self.player currentItem].presentationSize; + CGFloat width = size.width; + CGFloat height = size.height; + + // The player has not yet initialized. + if (height == CGSizeZero.height && width == CGSizeZero.width) { + return; + } + // The player may be initialized but still needs to determine the duration. + if ([self duration] == 0) { + return; + } + + _isInitialized = true; + _eventSink(@{ + @"event" : @"initialized", + @"duration" : @([self duration]), + @"width" : @(width), + @"height" : @(height) + }); + } +} + +- (void)play { + _isPlaying = true; + [self updatePlayingState]; +} + +- (void)pause { + _isPlaying = false; + [self updatePlayingState]; +} + +- (int64_t)position { + return FLTCMTimeToMillis([_player currentTime]); +} + +- (int64_t)duration { + return FLTCMTimeToMillis([[_player currentItem] duration]); +} + +- (void)seekTo:(int)location { + [_player seekToTime:CMTimeMake(location, 1000) + toleranceBefore:kCMTimeZero + toleranceAfter:kCMTimeZero]; +} + +- (void)setIsLooping:(bool)isLooping { + _isLooping = isLooping; +} + +- (void)setVolume:(double)volume { + _player.volume = (float)((volume < 0.0) ? 0.0 : ((volume > 1.0) ? 1.0 : volume)); +} + +- (void)setPlaybackSpeed:(double)speed { + // See https://developer.apple.com/library/archive/qa/qa1772/_index.html for an explanation of + // these checks. + if (speed > 2.0 && !_player.currentItem.canPlayFastForward) { + if (_eventSink != nil) { + _eventSink([FlutterError errorWithCode:@"VideoError" + message:@"Video cannot be fast-forwarded beyond 2.0x" + details:nil]); + } + return; + } + + if (speed < 1.0 && !_player.currentItem.canPlaySlowForward) { + if (_eventSink != nil) { + _eventSink([FlutterError errorWithCode:@"VideoError" + message:@"Video cannot be slow-forwarded" + details:nil]); + } + return; + } + + _player.rate = speed; +} + +- (CVPixelBufferRef)copyPixelBuffer { + CMTime outputItemTime = [_videoOutput itemTimeForHostTime:CACurrentMediaTime()]; + if ([_videoOutput hasNewPixelBufferForItemTime:outputItemTime]) { + return [_videoOutput copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL]; + } else { + return NULL; + } +} + +- (void)onTextureUnregistered:(NSObject*)texture { + dispatch_async(dispatch_get_main_queue(), ^{ + [self dispose]; + }); +} + +- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments { + _eventSink = nil; + return nil; +} + +- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments + eventSink:(nonnull FlutterEventSink)events { + _eventSink = events; + // TODO(@recastrodiaz): remove the line below when the race condition is resolved: + // https://github.com/flutter/flutter/issues/21483 + // This line ensures the 'initialized' event is sent when the event + // 'AVPlayerItemStatusReadyToPlay' fires before _eventSink is set (this function + // onListenWithArguments is called) + [self sendInitialized]; + return nil; +} + +/// This method allows you to dispose without touching the event channel. This +/// is useful for the case where the Engine is in the process of deconstruction +/// so the channel is going to die or is already dead. +- (void)disposeSansEventChannel { + _disposed = true; + [self stopDisplayLink]; + [[_player currentItem] removeObserver:self forKeyPath:@"status" context:statusContext]; + [[_player currentItem] removeObserver:self + forKeyPath:@"loadedTimeRanges" + context:timeRangeContext]; + [[_player currentItem] removeObserver:self + forKeyPath:@"playbackLikelyToKeepUp" + context:playbackLikelyToKeepUpContext]; + [[_player currentItem] removeObserver:self + forKeyPath:@"playbackBufferEmpty" + context:playbackBufferEmptyContext]; + [[_player currentItem] removeObserver:self + forKeyPath:@"playbackBufferFull" + context:playbackBufferFullContext]; + [_player replaceCurrentItemWithPlayerItem:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)dispose { + [self disposeSansEventChannel]; + [_eventChannel setStreamHandler:nil]; +} + +@end + +@interface FLTVideoPlayerPlugin () +@property(readonly, weak, nonatomic) NSObject* registry; +@property(readonly, weak, nonatomic) NSObject* messenger; +@property(readonly, strong, nonatomic) NSMutableDictionary* players; +@property(readonly, strong, nonatomic) NSObject* registrar; +@end + +@implementation FLTVideoPlayerPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + FLTVideoPlayerPlugin* instance = [[FLTVideoPlayerPlugin alloc] initWithRegistrar:registrar]; + // TODO(41471): This should be commented out when 41471's fix lands on stable. + //[registrar publish:instance]; + FLTVideoPlayerApiSetup(registrar.messenger, instance); +} + +- (instancetype)initWithRegistrar:(NSObject*)registrar { + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _registry = [registrar textures]; + _messenger = [registrar messenger]; + _registrar = registrar; + _players = [NSMutableDictionary dictionaryWithCapacity:1]; + return self; +} + +- (void)detachFromEngineForRegistrar:(NSObject*)registrar { + for (NSNumber* textureId in _players.allKeys) { + FLTVideoPlayer* player = _players[textureId]; + [player disposeSansEventChannel]; + } + [_players removeAllObjects]; + // TODO(57151): This should be commented out when 57151's fix lands on stable. + // This is the correct behavior we never did it in the past and the engine + // doesn't currently support it. + // FLTVideoPlayerApiSetup(registrar.messenger, nil); +} + +- (FLTTextureMessage*)onPlayerSetup:(FLTVideoPlayer*)player + frameUpdater:(FLTFrameUpdater*)frameUpdater { + int64_t textureId = [_registry registerTexture:player]; + frameUpdater.textureId = textureId; + FlutterEventChannel* eventChannel = [FlutterEventChannel + eventChannelWithName:[NSString stringWithFormat:@"flutter.io/videoPlayer/videoEvents%lld", + textureId] + binaryMessenger:_messenger]; + [eventChannel setStreamHandler:player]; + player.eventChannel = eventChannel; + _players[@(textureId)] = player; + FLTTextureMessage* result = [[FLTTextureMessage alloc] init]; + result.textureId = @(textureId); + return result; +} + +- (void)initialize:(FlutterError* __autoreleasing*)error { + for (NSNumber* textureId in _players) { + [_registry unregisterTexture:[textureId unsignedIntegerValue]]; + [_players[textureId] dispose]; + } + [_players removeAllObjects]; +} + +- (FLTTextureMessage*)create:(FLTCreateMessage*)input error:(FlutterError**)error { + FLTFrameUpdater* frameUpdater = [[FLTFrameUpdater alloc] initWithRegistry:_registry]; + FLTVideoPlayer* player; + if (input.uri) { + player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:input.uri] + frameUpdater:frameUpdater]; + return [self onPlayerSetup:player frameUpdater:frameUpdater]; + } else { + *error = [FlutterError errorWithCode:@"video_player" message:@"not implemented" details:nil]; + return nil; + } +} + +- (void)dispose:(FLTTextureMessage*)input error:(FlutterError**)error { + FLTVideoPlayer* player = _players[input.textureId]; + [_registry unregisterTexture:input.textureId.intValue]; + [_players removeObjectForKey:input.textureId]; + // If the Flutter contains https://github.com/flutter/engine/pull/12695, + // the `player` is disposed via `onTextureUnregistered` at the right time. + // Without https://github.com/flutter/engine/pull/12695, there is no guarantee that the + // texture has completed the un-reregistration. It may leads a crash if we dispose the + // `player` before the texture is unregistered. We add a dispatch_after hack to make sure the + // texture is unregistered before we dispose the `player`. + // + // TODO(cyanglaz): Remove this dispatch block when + // https://github.com/flutter/flutter/commit/8159a9906095efc9af8b223f5e232cb63542ad0b is in + // stable And update the min flutter version of the plugin to the stable version. + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + if (!player.disposed) { + [player dispose]; + } + }); +} + +- (void)setLooping:(FLTLoopingMessage*)input error:(FlutterError**)error { + FLTVideoPlayer* player = _players[input.textureId]; + [player setIsLooping:[input.isLooping boolValue]]; +} + +- (void)setVolume:(FLTVolumeMessage*)input error:(FlutterError**)error { + FLTVideoPlayer* player = _players[input.textureId]; + [player setVolume:[input.volume doubleValue]]; +} + +- (void)setPlaybackSpeed:(FLTPlaybackSpeedMessage*)input error:(FlutterError**)error { + FLTVideoPlayer* player = _players[input.textureId]; + [player setPlaybackSpeed:[input.speed doubleValue]]; +} + +- (void)play:(FLTTextureMessage*)input error:(FlutterError**)error { + FLTVideoPlayer* player = _players[input.textureId]; + [player play]; +} + +- (FLTPositionMessage*)position:(FLTTextureMessage*)input error:(FlutterError**)error { + FLTVideoPlayer* player = _players[input.textureId]; + FLTPositionMessage* result = [[FLTPositionMessage alloc] init]; + result.position = @([player position]); + return result; +} + +- (void)seekTo:(FLTPositionMessage*)input error:(FlutterError**)error { + FLTVideoPlayer* player = _players[input.textureId]; + [player seekTo:[input.position intValue]]; +} + +- (void)pause:(FLTTextureMessage*)input error:(FlutterError**)error { + FLTVideoPlayer* player = _players[input.textureId]; + [player pause]; +} + +- (void)setMixWithOthers:(FLTMixWithOthersMessage*)input + error:(FlutterError* _Nullable __autoreleasing*)error { +} + +@end diff --git a/packages/video_player/video_player_macos/macos/Classes/messages.h b/packages/video_player/video_player_macos/macos/Classes/messages.h new file mode 100644 index 000000000000..80137c9d61f5 --- /dev/null +++ b/packages/video_player/video_player_macos/macos/Classes/messages.h @@ -0,0 +1,74 @@ +// Autogenerated from Pigeon (v0.1.19), do not edit directly. +// See also: https://pub.dev/packages/pigeon +#import +@protocol FlutterBinaryMessenger; +@class FlutterError; +@class FlutterStandardTypedData; + +NS_ASSUME_NONNULL_BEGIN + +@class FLTTextureMessage; +@class FLTCreateMessage; +@class FLTLoopingMessage; +@class FLTVolumeMessage; +@class FLTPlaybackSpeedMessage; +@class FLTPositionMessage; +@class FLTMixWithOthersMessage; + +@interface FLTTextureMessage : NSObject +@property(nonatomic, strong, nullable) NSNumber *textureId; +@end + +@interface FLTCreateMessage : NSObject +@property(nonatomic, copy, nullable) NSString *asset; +@property(nonatomic, copy, nullable) NSString *uri; +@property(nonatomic, copy, nullable) NSString *packageName; +@property(nonatomic, copy, nullable) NSString *formatHint; +@end + +@interface FLTLoopingMessage : NSObject +@property(nonatomic, strong, nullable) NSNumber *textureId; +@property(nonatomic, strong, nullable) NSNumber *isLooping; +@end + +@interface FLTVolumeMessage : NSObject +@property(nonatomic, strong, nullable) NSNumber *textureId; +@property(nonatomic, strong, nullable) NSNumber *volume; +@end + +@interface FLTPlaybackSpeedMessage : NSObject +@property(nonatomic, strong, nullable) NSNumber *textureId; +@property(nonatomic, strong, nullable) NSNumber *speed; +@end + +@interface FLTPositionMessage : NSObject +@property(nonatomic, strong, nullable) NSNumber *textureId; +@property(nonatomic, strong, nullable) NSNumber *position; +@end + +@interface FLTMixWithOthersMessage : NSObject +@property(nonatomic, strong, nullable) NSNumber *mixWithOthers; +@end + +@protocol FLTVideoPlayerApi +- (void)initialize:(FlutterError *_Nullable *_Nonnull)error; +- (nullable FLTTextureMessage *)create:(FLTCreateMessage *)input + error:(FlutterError *_Nullable *_Nonnull)error; +- (void)dispose:(FLTTextureMessage *)input error:(FlutterError *_Nullable *_Nonnull)error; +- (void)setLooping:(FLTLoopingMessage *)input error:(FlutterError *_Nullable *_Nonnull)error; +- (void)setVolume:(FLTVolumeMessage *)input error:(FlutterError *_Nullable *_Nonnull)error; +- (void)setPlaybackSpeed:(FLTPlaybackSpeedMessage *)input + error:(FlutterError *_Nullable *_Nonnull)error; +- (void)play:(FLTTextureMessage *)input error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable FLTPositionMessage *)position:(FLTTextureMessage *)input + error:(FlutterError *_Nullable *_Nonnull)error; +- (void)seekTo:(FLTPositionMessage *)input error:(FlutterError *_Nullable *_Nonnull)error; +- (void)pause:(FLTTextureMessage *)input error:(FlutterError *_Nullable *_Nonnull)error; +- (void)setMixWithOthers:(FLTMixWithOthersMessage *)input + error:(FlutterError *_Nullable *_Nonnull)error; +@end + +extern void FLTVideoPlayerApiSetup(id binaryMessenger, + id _Nullable api); + +NS_ASSUME_NONNULL_END diff --git a/packages/video_player/video_player_macos/macos/Classes/messages.m b/packages/video_player/video_player_macos/macos/Classes/messages.m new file mode 100644 index 000000000000..d47f7ea0c863 --- /dev/null +++ b/packages/video_player/video_player_macos/macos/Classes/messages.m @@ -0,0 +1,365 @@ +// Autogenerated from Pigeon (v0.1.19), do not edit directly. +// See also: https://pub.dev/packages/pigeon +#import "messages.h" +#import + +#if !__has_feature(objc_arc) +#error File requires ARC to be enabled. +#endif + +#ifndef __clang_analyzer__ +static NSDictionary *wrapResult(NSDictionary *result, FlutterError *error) { + NSDictionary *errorDict = (NSDictionary *)[NSNull null]; + if (error) { + errorDict = [NSDictionary + dictionaryWithObjectsAndKeys:(error.code ? error.code : [NSNull null]), @"code", + (error.message ? error.message : [NSNull null]), @"message", + (error.details ? error.details : [NSNull null]), @"details", + nil]; + } + return [NSDictionary dictionaryWithObjectsAndKeys:(result ? result : [NSNull null]), @"result", + errorDict, @"error", nil]; +} + +@interface FLTTextureMessage () ++ (FLTTextureMessage *)fromMap:(NSDictionary *)dict; +- (NSDictionary *)toMap; +@end +@interface FLTCreateMessage () ++ (FLTCreateMessage *)fromMap:(NSDictionary *)dict; +- (NSDictionary *)toMap; +@end +@interface FLTLoopingMessage () ++ (FLTLoopingMessage *)fromMap:(NSDictionary *)dict; +- (NSDictionary *)toMap; +@end +@interface FLTVolumeMessage () ++ (FLTVolumeMessage *)fromMap:(NSDictionary *)dict; +- (NSDictionary *)toMap; +@end +@interface FLTPlaybackSpeedMessage () ++ (FLTPlaybackSpeedMessage *)fromMap:(NSDictionary *)dict; +- (NSDictionary *)toMap; +@end +@interface FLTPositionMessage () ++ (FLTPositionMessage *)fromMap:(NSDictionary *)dict; +- (NSDictionary *)toMap; +@end +@interface FLTMixWithOthersMessage () ++ (FLTMixWithOthersMessage *)fromMap:(NSDictionary *)dict; +- (NSDictionary *)toMap; +@end + +@implementation FLTTextureMessage ++ (FLTTextureMessage *)fromMap:(NSDictionary *)dict { + FLTTextureMessage *result = [[FLTTextureMessage alloc] init]; + result.textureId = dict[@"textureId"]; + if ((NSNull *)result.textureId == [NSNull null]) { + result.textureId = nil; + } + return result; +} +- (NSDictionary *)toMap { + return + [NSDictionary dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), + @"textureId", nil]; +} +@end + +@implementation FLTCreateMessage ++ (FLTCreateMessage *)fromMap:(NSDictionary *)dict { + FLTCreateMessage *result = [[FLTCreateMessage alloc] init]; + result.asset = dict[@"asset"]; + if ((NSNull *)result.asset == [NSNull null]) { + result.asset = nil; + } + result.uri = dict[@"uri"]; + if ((NSNull *)result.uri == [NSNull null]) { + result.uri = nil; + } + result.packageName = dict[@"packageName"]; + if ((NSNull *)result.packageName == [NSNull null]) { + result.packageName = nil; + } + result.formatHint = dict[@"formatHint"]; + if ((NSNull *)result.formatHint == [NSNull null]) { + result.formatHint = nil; + } + return result; +} +- (NSDictionary *)toMap { + return [NSDictionary + dictionaryWithObjectsAndKeys:(self.asset ? self.asset : [NSNull null]), @"asset", + (self.uri ? self.uri : [NSNull null]), @"uri", + (self.packageName ? self.packageName : [NSNull null]), + @"packageName", + (self.formatHint ? self.formatHint : [NSNull null]), + @"formatHint", nil]; +} +@end + +@implementation FLTLoopingMessage ++ (FLTLoopingMessage *)fromMap:(NSDictionary *)dict { + FLTLoopingMessage *result = [[FLTLoopingMessage alloc] init]; + result.textureId = dict[@"textureId"]; + if ((NSNull *)result.textureId == [NSNull null]) { + result.textureId = nil; + } + result.isLooping = dict[@"isLooping"]; + if ((NSNull *)result.isLooping == [NSNull null]) { + result.isLooping = nil; + } + return result; +} +- (NSDictionary *)toMap { + return [NSDictionary + dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", + (self.isLooping ? self.isLooping : [NSNull null]), @"isLooping", + nil]; +} +@end + +@implementation FLTVolumeMessage ++ (FLTVolumeMessage *)fromMap:(NSDictionary *)dict { + FLTVolumeMessage *result = [[FLTVolumeMessage alloc] init]; + result.textureId = dict[@"textureId"]; + if ((NSNull *)result.textureId == [NSNull null]) { + result.textureId = nil; + } + result.volume = dict[@"volume"]; + if ((NSNull *)result.volume == [NSNull null]) { + result.volume = nil; + } + return result; +} +- (NSDictionary *)toMap { + return [NSDictionary + dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", + (self.volume ? self.volume : [NSNull null]), @"volume", nil]; +} +@end + +@implementation FLTPlaybackSpeedMessage ++ (FLTPlaybackSpeedMessage *)fromMap:(NSDictionary *)dict { + FLTPlaybackSpeedMessage *result = [[FLTPlaybackSpeedMessage alloc] init]; + result.textureId = dict[@"textureId"]; + if ((NSNull *)result.textureId == [NSNull null]) { + result.textureId = nil; + } + result.speed = dict[@"speed"]; + if ((NSNull *)result.speed == [NSNull null]) { + result.speed = nil; + } + return result; +} +- (NSDictionary *)toMap { + return [NSDictionary + dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", + (self.speed ? self.speed : [NSNull null]), @"speed", nil]; +} +@end + +@implementation FLTPositionMessage ++ (FLTPositionMessage *)fromMap:(NSDictionary *)dict { + FLTPositionMessage *result = [[FLTPositionMessage alloc] init]; + result.textureId = dict[@"textureId"]; + if ((NSNull *)result.textureId == [NSNull null]) { + result.textureId = nil; + } + result.position = dict[@"position"]; + if ((NSNull *)result.position == [NSNull null]) { + result.position = nil; + } + return result; +} +- (NSDictionary *)toMap { + return [NSDictionary + dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", + (self.position ? self.position : [NSNull null]), @"position", + nil]; +} +@end + +@implementation FLTMixWithOthersMessage ++ (FLTMixWithOthersMessage *)fromMap:(NSDictionary *)dict { + FLTMixWithOthersMessage *result = [[FLTMixWithOthersMessage alloc] init]; + result.mixWithOthers = dict[@"mixWithOthers"]; + if ((NSNull *)result.mixWithOthers == [NSNull null]) { + result.mixWithOthers = nil; + } + return result; +} +- (NSDictionary *)toMap { + return [NSDictionary + dictionaryWithObjectsAndKeys:(self.mixWithOthers ? self.mixWithOthers : [NSNull null]), + @"mixWithOthers", nil]; +} +@end + +void FLTVideoPlayerApiSetup(id binaryMessenger, id api) { + { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.initialize" + binaryMessenger:binaryMessenger]; + if (api) { + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api initialize:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.create" + binaryMessenger:binaryMessenger]; + if (api) { + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + FLTCreateMessage *input = [FLTCreateMessage fromMap:message]; + FLTTextureMessage *output = [api create:input error:&error]; + callback(wrapResult([output toMap], error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.dispose" + binaryMessenger:binaryMessenger]; + if (api) { + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + FLTTextureMessage *input = [FLTTextureMessage fromMap:message]; + [api dispose:input error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.setLooping" + binaryMessenger:binaryMessenger]; + if (api) { + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + FLTLoopingMessage *input = [FLTLoopingMessage fromMap:message]; + [api setLooping:input error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.setVolume" + binaryMessenger:binaryMessenger]; + if (api) { + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + FLTVolumeMessage *input = [FLTVolumeMessage fromMap:message]; + [api setVolume:input error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed" + binaryMessenger:binaryMessenger]; + if (api) { + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + FLTPlaybackSpeedMessage *input = [FLTPlaybackSpeedMessage fromMap:message]; + [api setPlaybackSpeed:input error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = + [FlutterBasicMessageChannel messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.play" + binaryMessenger:binaryMessenger]; + if (api) { + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + FLTTextureMessage *input = [FLTTextureMessage fromMap:message]; + [api play:input error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.position" + binaryMessenger:binaryMessenger]; + if (api) { + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + FLTTextureMessage *input = [FLTTextureMessage fromMap:message]; + FLTPositionMessage *output = [api position:input error:&error]; + callback(wrapResult([output toMap], error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.seekTo" + binaryMessenger:binaryMessenger]; + if (api) { + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + FLTPositionMessage *input = [FLTPositionMessage fromMap:message]; + [api seekTo:input error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.pause" + binaryMessenger:binaryMessenger]; + if (api) { + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + FLTTextureMessage *input = [FLTTextureMessage fromMap:message]; + [api pause:input error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers" + binaryMessenger:binaryMessenger]; + if (api) { + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + FLTMixWithOthersMessage *input = [FLTMixWithOthersMessage fromMap:message]; + [api setMixWithOthers:input error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } +} +#endif diff --git a/packages/video_player/video_player_macos/macos/video_player_macos.podspec b/packages/video_player/video_player_macos/macos/video_player_macos.podspec new file mode 100644 index 000000000000..7a78c167a524 --- /dev/null +++ b/packages/video_player/video_player_macos/macos/video_player_macos.podspec @@ -0,0 +1,24 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'video_player_macos' + s.version = '0.0.1' + s.summary = 'Flutter macOS Video Player' + s.description = <<-DESC +A Flutter plugin for playing back video on a Widget surface. +Downloaded by pub (not CocoaPods). + DESC + s.homepage = 'https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_macos' + s.license = { :type => 'BSD', :file => '../LICENSE' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :http => 'https://github.com/flutter/plugins/tree/master/packages/video_player/video_player' } + s.documentation_url = 'https://pub.dev/packages/video_player' + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'FlutterMacOS' + + s.platform = :osx, '10.11' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } +end + diff --git a/packages/video_player/video_player_macos/pubspec.yaml b/packages/video_player/video_player_macos/pubspec.yaml new file mode 100644 index 000000000000..af083ec7578c --- /dev/null +++ b/packages/video_player/video_player_macos/pubspec.yaml @@ -0,0 +1,24 @@ +name: video_player_macos +description: macOS implementation of the video_player plugin. +version: 0.0.1 +homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_macos + + +flutter: + plugin: + implements: video_player + platforms: + macos: + pluginClass: FLTVideoPlayerPlugin + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.20.0" + +dependencies: + flutter: + sdk: flutter + video_player_platform_interface: ^4.0.0 + +dev_dependencies: + pedantic: ^1.10.0