From 355b90ee95d62735d98c33ca517565bf057efc14 Mon Sep 17 00:00:00 2001 From: Ben Hagen Date: Thu, 28 Nov 2019 13:15:48 +0100 Subject: [PATCH 1/5] Add web implementation --- .../video_player_web/CHANGELOG.md | 3 + .../video_player/video_player_web/LICENSE | 27 +++ .../video_player/video_player_web/README.md | 25 +++ .../ios/video_player_web.podspec | 20 ++ .../lib/video_player_web.dart | 196 ++++++++++++++++++ .../video_player_web/pubspec.yaml | 30 +++ .../test/video_player_web_test.dart | 91 ++++++++ 7 files changed, 392 insertions(+) create mode 100644 packages/video_player/video_player_web/CHANGELOG.md create mode 100644 packages/video_player/video_player_web/LICENSE create mode 100644 packages/video_player/video_player_web/README.md create mode 100644 packages/video_player/video_player_web/ios/video_player_web.podspec create mode 100644 packages/video_player/video_player_web/lib/video_player_web.dart create mode 100644 packages/video_player/video_player_web/pubspec.yaml create mode 100644 packages/video_player/video_player_web/test/video_player_web_test.dart diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md new file mode 100644 index 000000000000..29f648fd17f1 --- /dev/null +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* Initial release diff --git a/packages/video_player/video_player_web/LICENSE b/packages/video_player/video_player_web/LICENSE new file mode 100644 index 000000000000..c89293372cf3 --- /dev/null +++ b/packages/video_player/video_player_web/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/video_player/video_player_web/README.md b/packages/video_player/video_player_web/README.md new file mode 100644 index 000000000000..02a3eb99f9ed --- /dev/null +++ b/packages/video_player/video_player_web/README.md @@ -0,0 +1,25 @@ +# video_player_web + +The web implementation of [`video_player`][1]. + +## Usage + +To use this plugin in your Flutter Web app, simply add it as a dependency in +your pubspec using a `git` dependency. This is only temporary: in the future +we hope to make this package an "endorsed" implementation of `video_player`, +so that it is automatically included in your Flutter Web app when you depend +on `package:video_player`. + +```yaml +dependencies: + video_player: ^0.10.4 + video_player_web: + git: + url: git://github.com/flutter/plugins.git + path: packages/video_player/video_player_web +``` + +Once you have the `video_player_web` dependency in your pubspec, you should +be able to use `package:video_player` as normal. + +[1]: ../video_player \ No newline at end of file diff --git a/packages/video_player/video_player_web/ios/video_player_web.podspec b/packages/video_player/video_player_web/ios/video_player_web.podspec new file mode 100644 index 000000000000..5129b7c69032 --- /dev/null +++ b/packages/video_player/video_player_web/ios/video_player_web.podspec @@ -0,0 +1,20 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'video_player_web' + s.version = '0.0.1' + s.summary = 'No-op implementation of video_player_web web plugin to avoid build issues on iOS' + s.description = <<-DESC +temp fake video_player_web plugin + DESC + s.homepage = 'https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_web' + s.license = { :file => '../LICENSE' } + s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + + s.ios.deployment_target = '8.0' +end \ No newline at end of file diff --git a/packages/video_player/video_player_web/lib/video_player_web.dart b/packages/video_player/video_player_web/lib/video_player_web.dart new file mode 100644 index 000000000000..98f464899dbf --- /dev/null +++ b/packages/video_player/video_player_web/lib/video_player_web.dart @@ -0,0 +1,196 @@ +import 'dart:async'; +import 'dart:html'; +import 'dart:ui' as ui; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:video_player_platform_interface/video_player_platform_interface.dart'; + +/// The web implementation of [VideoPlayerPlatform]. +/// +/// This class implements the `package:video_player` functionality for the web. +class VideoPlayerPlugin extends VideoPlayerPlatform { + /// Registers this class as the default instance of [VideoPlayerPlatform]. + static void registerWith(Registrar registrar) { + VideoPlayerPlatform.instance = VideoPlayerPlugin(); + } + + Map _videoPlayers = {}; + + int _textureCounter = 1; + + @override + Future init() async { + return _disposeAllPlayers(); + } + + @override + Future dispose(int textureId) async { + _videoPlayers[textureId].dispose(); + _videoPlayers.remove(textureId); + return null; + } + + void _disposeAllPlayers() { + _videoPlayers + .forEach((_, _VideoPlayer videoPlayer) => videoPlayer.dispose()); + _videoPlayers.clear(); + } + + @override + Future create(DataSource dataSource) async { + final int textureId = _textureCounter; + _textureCounter++; + + final _VideoPlayer player = _VideoPlayer( + uri: Uri.parse(dataSource.uri), + textureId: textureId, + ); + + player.setupVideoPlayer(); + + _videoPlayers[textureId] = player; + return textureId; + } + + @override + Future setLooping(int textureId, bool looping) async { + return _videoPlayers[textureId].setLooping(looping); + } + + @override + Future play(int textureId) async { + return _videoPlayers[textureId].play(); + } + + @override + Future pause(int textureId) async { + return _videoPlayers[textureId].pause(); + } + + @override + Future setVolume(int textureId, double volume) async { + return _videoPlayers[textureId].setVolume(volume); + } + + @override + Future seekTo(int textureId, Duration position) async { + return _videoPlayers[textureId].seekTo(position); + } + + @override + Future getPosition(int textureId) async { + _videoPlayers[textureId].sendBufferingUpdate(); + return _videoPlayers[textureId].getPosition(); + } + + @override + Stream videoEventsFor(int textureId) { + return _videoPlayers[textureId].eventController.stream; + } + + @override + Widget buildView(int textureId) { + return HtmlElementView(viewType: textureId.toString()); + } +} + +class _VideoPlayer { + _VideoPlayer({this.uri, this.textureId}); + + final StreamController eventController = + StreamController(); + + final Uri uri; + final int textureId; + VideoElement videoElement; + bool isInitialized = false; + + void setupVideoPlayer() { + videoElement = VideoElement() + ..src = uri.toString() + ..autoplay = false + ..controls = false + ..style.border = 'none'; + + // ignore: undefined_prefixed_name + ui.platformViewRegistry.registerViewFactory( + textureId.toString(), (int viewId) => videoElement); + + videoElement.onCanPlay.listen((dynamic _) { + if (!isInitialized) { + isInitialized = true; + sendInitialized(); + } + }); + videoElement.onError.listen((dynamic error) { + eventController.addError(error); + }); + videoElement.onEnded.listen((dynamic _) { + eventController.add(VideoEvent(eventType: VideoEventType.completed)); + }); + } + + void sendBufferingUpdate() { + eventController.add(VideoEvent( + buffered: _toDurationRange(videoElement.buffered), + eventType: VideoEventType.completed, + )); + } + + void play() { + videoElement.play(); + } + + void pause() { + videoElement.pause(); + } + + void setLooping(bool value) { + videoElement.loop = value; + } + + void setVolume(double value) { + videoElement.volume = value; + } + + void seekTo(Duration position) { + videoElement.currentTime = position.inMilliseconds.toDouble() / 1000; + } + + Duration getPosition() { + return Duration(milliseconds: (videoElement.currentTime * 1000).round()); + } + + void sendInitialized() { + eventController.add( + VideoEvent( + eventType: VideoEventType.initialized, + duration: Duration( + milliseconds: (videoElement.duration * 1000).round(), + ), + size: Size( + videoElement.videoWidth.toDouble() ?? 0.0, + videoElement.videoHeight.toDouble() ?? 0.0, + ), + ), + ); + } + + void dispose() { + videoElement.removeAttribute('src'); + videoElement.load(); + } + + List _toDurationRange(TimeRanges buffered) { + final List durationRange = []; + for (int i = 0; i < buffered.length; i++) { + durationRange.add(DurationRange( + Duration(milliseconds: (buffered.start(i) * 1000).round()), + Duration(milliseconds: (buffered.end(i) * 1000).round()), + )); + } + return durationRange; + } +} diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml new file mode 100644 index 000000000000..70325a16516a --- /dev/null +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -0,0 +1,30 @@ +name: video_player_web +description: Web platform implementation of video_player +author: Flutter Team +homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_web +version: 0.0.1 + +flutter: + plugin: + platforms: + web: + pluginClass: VideoPlayerPlugin + fileName: video_player_web.dart + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + meta: ^1.1.7 + video_player_platform_interface: ^1.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + video_player: + path: ../video_player + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.5.0 <2.0.0" \ No newline at end of file diff --git a/packages/video_player/video_player_web/test/video_player_web_test.dart b/packages/video_player/video_player_web/test/video_player_web_test.dart new file mode 100644 index 000000000000..efe115d02e5f --- /dev/null +++ b/packages/video_player/video_player_web/test/video_player_web_test.dart @@ -0,0 +1,91 @@ +// Copyright 2019 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. +@TestOn('browser') + +import 'dart:async'; + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:video_player/video_player.dart'; +import 'package:video_player_platform_interface/video_player_platform_interface.dart'; +import 'package:video_player_web/video_player_web.dart'; + +void main() { + group('VideoPlayer for Web', () { + int textureId; + + setUp(() async { + VideoPlayerPlatform.instance = VideoPlayerPlugin(); + textureId = await VideoPlayerPlatform.instance.create( + DataSource( + sourceType: DataSourceType.network, + uri: + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'), + ); + }); + + test('$VideoPlayerPlugin is the live instance', () { + expect(VideoPlayerPlatform.instance, isA()); + }); + + test('can init', () { + expect(VideoPlayerPlatform.instance.init(), completes); + }); + + test('can create', () { + expect( + VideoPlayerPlatform.instance.create( + DataSource( + sourceType: DataSourceType.network, + uri: + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'), + ), + completion(isNonZero)); + }); + + test('can dispose', () { + expect(VideoPlayerPlatform.instance.dispose(textureId), completes); + }); + + test('can set looping', () { + expect( + VideoPlayerPlatform.instance.setLooping(textureId, true), completes); + }); + + test('can play', () async { + // Mute video to allow autoplay (See https://goo.gl/xX8pDD) + await VideoPlayerPlatform.instance.setVolume(textureId, 0); + expect(VideoPlayerPlatform.instance.play(textureId), completes); + }); + + test('can pause', () { + expect(VideoPlayerPlatform.instance.pause(textureId), completes); + }); + + test('can set volume', () { + expect(VideoPlayerPlatform.instance.setVolume(textureId, 0.8), completes); + }); + + test('can seek to position', () { + expect( + VideoPlayerPlatform.instance.seekTo(textureId, Duration(seconds: 1)), + completes); + }); + + test('can get position', () { + expect(VideoPlayerPlatform.instance.getPosition(textureId), + completion(isInstanceOf())); + }); + + test('can get video event stream', () { + expect(VideoPlayerPlatform.instance.videoEventsFor(textureId), + isInstanceOf>()); + }); + + test('can build view', () { + expect(VideoPlayerPlatform.instance.buildView(textureId), + isInstanceOf()); + }); + }); +} From 6a7e313b9c41861715b9745c178f23e3a1f7dc38 Mon Sep 17 00:00:00 2001 From: Ben Hagen Date: Sun, 1 Dec 2019 15:18:37 +0100 Subject: [PATCH 2/5] Address review comments --- packages/video_player/video_player_web/CHANGELOG.md | 2 +- .../video_player_web/lib/video_player_web.dart | 10 +++++----- packages/video_player/video_player_web/pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index 29f648fd17f1..1318780830f8 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,3 +1,3 @@ -## 0.0.1 +## 0.1.0 * Initial release diff --git a/packages/video_player/video_player_web/lib/video_player_web.dart b/packages/video_player/video_player_web/lib/video_player_web.dart index 98f464899dbf..fbd4abc9294a 100644 --- a/packages/video_player/video_player_web/lib/video_player_web.dart +++ b/packages/video_player/video_player_web/lib/video_player_web.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:html'; import 'dart:ui' as ui; -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; @@ -33,8 +32,8 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { } void _disposeAllPlayers() { - _videoPlayers - .forEach((_, _VideoPlayer videoPlayer) => videoPlayer.dispose()); + _videoPlayers.values + .forEach((_VideoPlayer videoPlayer) => videoPlayer.dispose()); _videoPlayers.clear(); } @@ -92,7 +91,7 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { @override Widget buildView(int textureId) { - return HtmlElementView(viewType: textureId.toString()); + return HtmlElementView(viewType: 'videoPlayer-$textureId'); } } @@ -114,9 +113,10 @@ class _VideoPlayer { ..controls = false ..style.border = 'none'; + // TODO(hterkelsen): Use initialization parameters once they are available // ignore: undefined_prefixed_name ui.platformViewRegistry.registerViewFactory( - textureId.toString(), (int viewId) => videoElement); + 'videoPlayer-$textureId', (int viewId) => videoElement); videoElement.onCanPlay.listen((dynamic _) { if (!isInitialized) { diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index 70325a16516a..89023d16ec8f 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_web description: Web platform implementation of video_player author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_web -version: 0.0.1 +version: 0.1.0 flutter: plugin: From 17785bd34dfe48f080f8b243eb8a1a1844a902dc Mon Sep 17 00:00:00 2001 From: Ben Hagen Date: Tue, 3 Dec 2019 15:56:49 +0100 Subject: [PATCH 3/5] Rename setupVideoPlayer() to initialize() --- .../video_player/video_player_web/lib/video_player_web.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/video_player/video_player_web/lib/video_player_web.dart b/packages/video_player/video_player_web/lib/video_player_web.dart index fbd4abc9294a..0e3ccbed7387 100644 --- a/packages/video_player/video_player_web/lib/video_player_web.dart +++ b/packages/video_player/video_player_web/lib/video_player_web.dart @@ -47,7 +47,7 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { textureId: textureId, ); - player.setupVideoPlayer(); + player.initialize(); _videoPlayers[textureId] = player; return textureId; @@ -106,7 +106,7 @@ class _VideoPlayer { VideoElement videoElement; bool isInitialized = false; - void setupVideoPlayer() { + void initialize() { videoElement = VideoElement() ..src = uri.toString() ..autoplay = false From 93509d433e2f51f41282c5cf18e56a77025ce107 Mon Sep 17 00:00:00 2001 From: Ben Hagen Date: Tue, 3 Dec 2019 17:52:33 +0100 Subject: [PATCH 4/5] Send correct VideoEventType --- .../video_player/video_player_web/lib/video_player_web.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/video_player/video_player_web/lib/video_player_web.dart b/packages/video_player/video_player_web/lib/video_player_web.dart index 0e3ccbed7387..24f8a91047c2 100644 --- a/packages/video_player/video_player_web/lib/video_player_web.dart +++ b/packages/video_player/video_player_web/lib/video_player_web.dart @@ -135,7 +135,7 @@ class _VideoPlayer { void sendBufferingUpdate() { eventController.add(VideoEvent( buffered: _toDurationRange(videoElement.buffered), - eventType: VideoEventType.completed, + eventType: VideoEventType.bufferingUpdate, )); } From 8349641df3eeb83b0c7da5195a6256d0e60742c2 Mon Sep 17 00:00:00 2001 From: Ben Hagen Date: Tue, 3 Dec 2019 18:07:46 +0100 Subject: [PATCH 5/5] Add autoplay note to README. --- packages/video_player/video_player_web/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/video_player/video_player_web/README.md b/packages/video_player/video_player_web/README.md index 02a3eb99f9ed..b8a441c3357c 100644 --- a/packages/video_player/video_player_web/README.md +++ b/packages/video_player/video_player_web/README.md @@ -22,4 +22,8 @@ dependencies: Once you have the `video_player_web` dependency in your pubspec, you should be able to use `package:video_player` as normal. +## Autoplay +Playing videos without prior interaction with the site might be prohibited +by the browser and lead to runtime errors. See also: https://goo.gl/xX8pDD. + [1]: ../video_player \ No newline at end of file