blob: 28046f42e9a89b16a987b6a8c51d6168a2d4225d [file] [log] [blame]
// Copyright 2013 The Flutter 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 'dart:async';
import 'dart:html' as html;
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:video_player_platform_interface/video_player_platform_interface.dart';
import 'package:video_player_web/src/duration_utils.dart';
import 'package:video_player_web/src/video_player.dart';
import 'utils.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('VideoPlayer', () {
late html.VideoElement video;
setUp(() {
// Never set "src" on the video, so this test doesn't hit the network!
video = html.VideoElement()
..controls = true
..setAttribute('playsinline', 'false');
});
testWidgets('fixes critical video element config', (WidgetTester _) async {
VideoPlayer(videoElement: video).initialize();
expect(video.controls, isFalse,
reason: 'Video is controlled through code');
expect(video.getAttribute('autoplay'), 'false',
reason: 'Cannot autoplay on the web');
expect(video.getAttribute('playsinline'), 'true',
reason: 'Needed by safari iOS');
});
testWidgets('setVolume', (WidgetTester tester) async {
final VideoPlayer player = VideoPlayer(videoElement: video)..initialize();
player.setVolume(0);
expect(video.volume, isZero, reason: 'Volume should be zero');
expect(video.muted, isTrue, reason: 'muted attribute should be true');
expect(() {
player.setVolume(-0.0001);
}, throwsAssertionError, reason: 'Volume cannot be < 0');
expect(() {
player.setVolume(1.0001);
}, throwsAssertionError, reason: 'Volume cannot be > 1');
});
testWidgets('setPlaybackSpeed', (WidgetTester tester) async {
final VideoPlayer player = VideoPlayer(videoElement: video)..initialize();
expect(() {
player.setPlaybackSpeed(-1);
}, throwsAssertionError, reason: 'Playback speed cannot be < 0');
expect(() {
player.setPlaybackSpeed(0);
}, throwsAssertionError, reason: 'Playback speed cannot be == 0');
});
testWidgets('seekTo', (WidgetTester tester) async {
final VideoPlayer player = VideoPlayer(videoElement: video)..initialize();
expect(() {
player.seekTo(const Duration(seconds: -1));
}, throwsAssertionError, reason: 'Cannot seek into negative numbers');
});
// The events tested in this group do *not* represent the actual sequence
// of events from a real "video" element. They're crafted to test the
// behavior of the VideoPlayer in different states with different events.
group('events', () {
late StreamController<VideoEvent> streamController;
late VideoPlayer player;
late Stream<VideoEvent> timedStream;
final Set<VideoEventType> bufferingEvents = <VideoEventType>{
VideoEventType.bufferingStart,
VideoEventType.bufferingEnd,
};
setUp(() {
streamController = StreamController<VideoEvent>();
player =
VideoPlayer(videoElement: video, eventController: streamController)
..initialize();
// This stream will automatically close after 100 ms without seeing any events
timedStream = streamController.stream.timeout(
const Duration(milliseconds: 100),
onTimeout: (EventSink<VideoEvent> sink) {
sink.close();
},
);
});
testWidgets('buffering dispatches only when it changes',
(WidgetTester tester) async {
// Take all the "buffering" events that we see during the next few seconds
final Future<List<bool>> stream = timedStream
.where(
(VideoEvent event) => bufferingEvents.contains(event.eventType))
.map((VideoEvent event) =>
event.eventType == VideoEventType.bufferingStart)
.toList();
// Simulate some events coming from the player...
player.setBuffering(true);
player.setBuffering(true);
player.setBuffering(true);
player.setBuffering(false);
player.setBuffering(false);
player.setBuffering(true);
player.setBuffering(false);
player.setBuffering(true);
player.setBuffering(false);
final List<bool> events = await stream;
expect(events, hasLength(6));
expect(events, <bool>[true, false, true, false, true, false]);
});
testWidgets('canplay event does not change buffering state',
(WidgetTester tester) async {
// Take all the "buffering" events that we see during the next few seconds
final Future<List<bool>> stream = timedStream
.where(
(VideoEvent event) => bufferingEvents.contains(event.eventType))
.map((VideoEvent event) =>
event.eventType == VideoEventType.bufferingStart)
.toList();
player.setBuffering(true);
// Simulate "canplay" event...
video.dispatchEvent(html.Event('canplay'));
final List<bool> events = await stream;
expect(events, hasLength(1));
expect(events, <bool>[true]);
});
testWidgets('canplaythrough event does change buffering state',
(WidgetTester tester) async {
// Take all the "buffering" events that we see during the next few seconds
final Future<List<bool>> stream = timedStream
.where(
(VideoEvent event) => bufferingEvents.contains(event.eventType))
.map((VideoEvent event) =>
event.eventType == VideoEventType.bufferingStart)
.toList();
player.setBuffering(true);
// Simulate "canplaythrough" event...
video.dispatchEvent(html.Event('canplaythrough'));
final List<bool> events = await stream;
expect(events, hasLength(2));
expect(events, <bool>[true, false]);
});
testWidgets('initialized dispatches only once',
(WidgetTester tester) async {
// Dispatch some bogus "canplay" events from the video object
video.dispatchEvent(html.Event('canplay'));
video.dispatchEvent(html.Event('canplay'));
video.dispatchEvent(html.Event('canplay'));
// Take all the "initialized" events that we see during the next few seconds
final Future<List<VideoEvent>> stream = timedStream
.where((VideoEvent event) =>
event.eventType == VideoEventType.initialized)
.toList();
video.dispatchEvent(html.Event('canplay'));
video.dispatchEvent(html.Event('canplay'));
video.dispatchEvent(html.Event('canplay'));
final List<VideoEvent> events = await stream;
expect(events, hasLength(1));
expect(events[0].eventType, VideoEventType.initialized);
});
// Issue: https://github.com/flutter/flutter/issues/105649
testWidgets('supports `Infinity` duration', (WidgetTester _) async {
setInfinityDuration(video);
expect(video.duration.isInfinite, isTrue);
final Future<List<VideoEvent>> stream = timedStream
.where((VideoEvent event) =>
event.eventType == VideoEventType.initialized)
.toList();
video.dispatchEvent(html.Event('canplay'));
final List<VideoEvent> events = await stream;
expect(events, hasLength(1));
expect(events[0].eventType, VideoEventType.initialized);
expect(events[0].duration, equals(jsCompatibleTimeUnset));
});
});
});
}