Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit f376060

Browse files
committed
Add web implementation
1 parent cba3932 commit f376060

7 files changed

Lines changed: 386 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 0.0.1
2+
3+
* Initial release
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2017 The Chromium Authors. All rights reserved.
2+
//
3+
// Redistribution and use in source and binary forms, with or without
4+
// modification, are permitted provided that the following conditions are
5+
// met:
6+
//
7+
// * Redistributions of source code must retain the above copyright
8+
// notice, this list of conditions and the following disclaimer.
9+
// * Redistributions in binary form must reproduce the above
10+
// copyright notice, this list of conditions and the following disclaimer
11+
// in the documentation and/or other materials provided with the
12+
// distribution.
13+
// * Neither the name of Google Inc. nor the names of its
14+
// contributors may be used to endorse or promote products derived from
15+
// this software without specific prior written permission.
16+
//
17+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# video_player_web
2+
3+
The web implementation of [`video_player`][1].
4+
5+
## Usage
6+
7+
To use this plugin in your Flutter Web app, simply add it as a dependency in
8+
your pubspec using a `git` dependency. This is only temporary: in the future
9+
we hope to make this package an "endorsed" implementation of `video_player`,
10+
so that it is automatically included in your Flutter Web app when you depend
11+
on `package:video_player`.
12+
13+
```yaml
14+
dependencies:
15+
video_player: ^0.10.4
16+
video_player_web:
17+
git:
18+
url: git://github.com/flutter/plugins.git
19+
path: packages/video_player/video_player_web
20+
```
21+
22+
Once you have the `video_player_web` dependency in your pubspec, you should
23+
be able to use `package:video_player` as normal.
24+
25+
[1]: ../video_player
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#
2+
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
3+
#
4+
Pod::Spec.new do |s|
5+
s.name = 'video_player_web'
6+
s.version = '0.0.1'
7+
s.summary = 'No-op implementation of video_player_web web plugin to avoid build issues on iOS'
8+
s.description = <<-DESC
9+
temp fake video_player_web plugin
10+
DESC
11+
s.homepage = 'https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_web'
12+
s.license = { :file => '../LICENSE' }
13+
s.author = { 'Flutter Team' => '[email protected]' }
14+
s.source = { :path => '.' }
15+
s.source_files = 'Classes/**/*'
16+
s.public_header_files = 'Classes/**/*.h'
17+
s.dependency 'Flutter'
18+
19+
s.ios.deployment_target = '8.0'
20+
end
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import 'dart:async';
2+
import 'dart:html';
3+
import 'dart:ui' as ui;
4+
import 'dart:ui';
5+
6+
import 'package:flutter/material.dart';
7+
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
8+
import 'package:video_player_platform_interface/video_player_platform_interface.dart';
9+
10+
class VideoPlayer {
11+
VideoPlayer({this.uri, this.textureId});
12+
13+
final StreamController<VideoEvent> eventController =
14+
StreamController<VideoEvent>();
15+
16+
final Uri uri;
17+
final int textureId;
18+
VideoElement videoElement;
19+
bool isInitialized = false;
20+
21+
void setupVideoPlayer() {
22+
videoElement = VideoElement()
23+
..src = uri.toString()
24+
..autoplay = false
25+
..controls = false
26+
..style.border = 'none';
27+
28+
// ignore: undefined_prefixed_name
29+
ui.platformViewRegistry.registerViewFactory(
30+
textureId.toString(), (int viewId) => videoElement);
31+
32+
videoElement.onCanPlay.listen((dynamic _) {
33+
if (!isInitialized) {
34+
isInitialized = true;
35+
sendInitialized();
36+
}
37+
});
38+
videoElement.onError.listen((dynamic error) {
39+
eventController.addError(error);
40+
});
41+
videoElement.onEnded.listen((dynamic _) {
42+
eventController.add(VideoEvent(eventType: VideoEventType.completed));
43+
});
44+
}
45+
46+
void sendBufferingUpdate() {
47+
eventController.add(VideoEvent(
48+
buffered: _toDurationRange(videoElement.buffered),
49+
eventType: VideoEventType.completed,
50+
));
51+
}
52+
53+
void play() {
54+
videoElement.play();
55+
}
56+
57+
void pause() {
58+
videoElement.pause();
59+
}
60+
61+
void setLooping(bool value) {
62+
videoElement.loop = value;
63+
}
64+
65+
void setVolume(double value) {
66+
videoElement.volume = value;
67+
}
68+
69+
void seekTo(Duration position) {
70+
videoElement.currentTime = position.inMilliseconds.toDouble() / 1000;
71+
}
72+
73+
Duration getPosition() {
74+
return Duration(milliseconds: (videoElement.currentTime * 1000).round());
75+
}
76+
77+
void sendInitialized() {
78+
eventController.add(
79+
VideoEvent(
80+
eventType: VideoEventType.initialized,
81+
duration: Duration(
82+
milliseconds: (videoElement.duration * 1000).round(),
83+
),
84+
size: Size(
85+
videoElement.videoWidth.toDouble() ?? 0.0,
86+
videoElement.videoHeight.toDouble() ?? 0.0,
87+
),
88+
),
89+
);
90+
}
91+
92+
void dispose() {
93+
videoElement.removeAttribute('src');
94+
videoElement.load();
95+
}
96+
97+
List<DurationRange> _toDurationRange(TimeRanges buffered) {
98+
final List<DurationRange> durationRange = <DurationRange>[];
99+
for (int i = 0; i < buffered.length; i++) {
100+
durationRange.add(DurationRange(
101+
Duration(milliseconds: (buffered.start(i) * 1000).round()),
102+
Duration(milliseconds: (buffered.end(i) * 1000).round()),
103+
));
104+
}
105+
return durationRange;
106+
}
107+
}
108+
109+
/// The web implementation of [VideoPlayerPlatform].
110+
///
111+
/// This class implements the `package:video_player` functionality for the web.
112+
class VideoPlayerPlugin extends VideoPlayerPlatform {
113+
/// Registers this class as the default instance of [VideoPlayerPlatform].
114+
static void registerWith(Registrar registrar) {
115+
VideoPlayerPlatform.instance = VideoPlayerPlugin();
116+
}
117+
118+
Map<int, VideoPlayer> videoPlayers = <int, VideoPlayer>{};
119+
Map<int, StreamController<VideoEvent>> videoEventControllers =
120+
<int, StreamController<VideoEvent>>{};
121+
122+
int _textureCounter = 1;
123+
124+
@override
125+
Future<void> init() async {
126+
return _disposeAllPlayers();
127+
}
128+
129+
@override
130+
Future<void> dispose(int textureId) {
131+
videoPlayers[textureId].dispose();
132+
videoPlayers.remove(textureId);
133+
return null;
134+
}
135+
136+
void _disposeAllPlayers() {
137+
videoPlayers.forEach((_, VideoPlayer videoPlayer) => videoPlayer.dispose());
138+
videoPlayers.clear();
139+
}
140+
141+
@override
142+
Future<int> create(DataSource dataSource) async {
143+
final int textureId = _textureCounter;
144+
_textureCounter++;
145+
146+
final VideoPlayer player = VideoPlayer(
147+
uri: Uri.parse(dataSource.uri),
148+
textureId: textureId,
149+
);
150+
151+
player.setupVideoPlayer();
152+
153+
videoPlayers[textureId] = player;
154+
return textureId;
155+
}
156+
157+
@override
158+
Future<void> setLooping(int textureId, bool looping) async {
159+
return videoPlayers[textureId].setLooping(looping);
160+
}
161+
162+
@override
163+
Future<void> play(int textureId) async {
164+
return videoPlayers[textureId].play();
165+
}
166+
167+
@override
168+
Future<void> pause(int textureId) async {
169+
return videoPlayers[textureId].pause();
170+
}
171+
172+
@override
173+
Future<void> setVolume(int textureId, double volume) async {
174+
return videoPlayers[textureId].setVolume(volume);
175+
}
176+
177+
@override
178+
Future<void> seekTo(int textureId, Duration position) async {
179+
return videoPlayers[textureId].seekTo(position);
180+
}
181+
182+
@override
183+
Future<Duration> getPosition(int textureId) async {
184+
videoPlayers[textureId].sendBufferingUpdate();
185+
return videoPlayers[textureId].getPosition();
186+
}
187+
188+
@override
189+
Stream<VideoEvent> videoEventsFor(int textureId) {
190+
return videoPlayers[textureId].eventController.stream;
191+
}
192+
193+
@override
194+
Widget buildView(int textureId) {
195+
return HtmlElementView(viewType: textureId.toString());
196+
}
197+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: video_player_web
2+
description: Web platform implementation of video_player
3+
author: Flutter Team <[email protected]>
4+
homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_web
5+
version: 0.0.1
6+
7+
flutter:
8+
plugin:
9+
platforms:
10+
web:
11+
pluginClass: VideoPlayerPlugin
12+
fileName: video_player_web.dart
13+
14+
dependencies:
15+
flutter:
16+
sdk: flutter
17+
flutter_web_plugins:
18+
sdk: flutter
19+
meta: ^1.1.7
20+
video_player_platform_interface: ^1.0.0
21+
22+
dev_dependencies:
23+
flutter_test:
24+
sdk: flutter
25+
video_player:
26+
path: ../video_player
27+
28+
environment:
29+
sdk: ">=2.0.0-dev.28.0 <3.0.0"
30+
flutter: ">=1.5.0 <2.0.0"
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'package:flutter/foundation.dart';
7+
import 'package:flutter/widgets.dart';
8+
import 'package:video_player/video_player.dart';
9+
import 'package:flutter_test/flutter_test.dart';
10+
import 'package:video_player_platform_interface/video_player_platform_interface.dart';
11+
12+
class FakeController extends ValueNotifier<VideoPlayerValue>
13+
implements VideoPlayerController {
14+
FakeController() : super(VideoPlayerValue(duration: null));
15+
16+
@override
17+
Future<void> dispose() async {
18+
super.dispose();
19+
}
20+
21+
@override
22+
int textureId;
23+
24+
@override
25+
String get dataSource => '';
26+
@override
27+
DataSourceType get dataSourceType => DataSourceType.file;
28+
@override
29+
String get package => null;
30+
@override
31+
Future<Duration> get position async => value.position;
32+
33+
@override
34+
Future<void> seekTo(Duration moment) async {}
35+
@override
36+
Future<void> setVolume(double volume) async {}
37+
@override
38+
Future<void> initialize() async {}
39+
@override
40+
Future<void> pause() async {}
41+
@override
42+
Future<void> play() async {}
43+
@override
44+
Future<void> setLooping(bool looping) async {}
45+
46+
@override
47+
VideoFormat get formatHint => null;
48+
}
49+
50+
void main() {
51+
testWidgets('update texture', (WidgetTester tester) async {
52+
final FakeController controller = FakeController();
53+
await tester.pumpWidget(VideoPlayer(controller));
54+
expect(find.byType(Texture), findsNothing);
55+
56+
controller.textureId = 123;
57+
controller.value = controller.value.copyWith(
58+
duration: const Duration(milliseconds: 100),
59+
);
60+
61+
await tester.pump();
62+
expect(find.byType(Texture), findsOneWidget);
63+
});
64+
65+
testWidgets('update controller', (WidgetTester tester) async {
66+
final FakeController controller1 = FakeController();
67+
controller1.textureId = 101;
68+
await tester.pumpWidget(VideoPlayer(controller1));
69+
expect(
70+
find.byWidgetPredicate(
71+
(Widget widget) => widget is Texture && widget.textureId == 101,
72+
),
73+
findsOneWidget);
74+
75+
final FakeController controller2 = FakeController();
76+
controller2.textureId = 102;
77+
await tester.pumpWidget(VideoPlayer(controller2));
78+
expect(
79+
find.byWidgetPredicate(
80+
(Widget widget) => widget is Texture && widget.textureId == 102,
81+
),
82+
findsOneWidget);
83+
});
84+
}

0 commit comments

Comments
 (0)