zeroratehls 1.0.7
zeroratehls: ^1.0.7 copied to clipboard
A comprehensive Flutter SDK for seamless HLS (HTTP Live Streaming) video playback. This package provides a robust, feature-rich player with advanced capabilities including adaptive bitrate streaming, [...]
example/lib/main.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:zeroratehls/models/zerorate_hls_player_events.dart';
import 'package:zeroratehls/models/zerorate_hls_player_options.dart';
import 'package:zeroratehls/utils/device_information.dart';
import 'package:zeroratehls_example/player_config.dart';
import 'package:zeroratehls/zerorate_hls_player.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
ZerorateHlsSDK.initialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: PlayerTestScreen(),
);
}
}
class PlayerTestScreen extends StatefulWidget {
const PlayerTestScreen({super.key});
@override
State<PlayerTestScreen> createState() => _PlayerTestScreenState();
}
class _PlayerTestScreenState extends State<PlayerTestScreen> {
ZerorateHlsPlayer? _player;
String _deviceInfo = 'Loading...';
List<String> _eventLog = [];
@override
void initState() {
super.initState();
_initializePlayer();
_loadDeviceInfo();
}
Future<void> _loadDeviceInfo() async {
final info = await DeviceInformation.getDeviceInfo(context);
if (mounted) {
setState(() {
final decoded = utf8.decode(base64Decode(info));
final Map<String, dynamic> deviceData = json.decode(decoded);
_deviceInfo = const JsonEncoder.withIndent(' ').convert(deviceData);
});
}
}
void _updatePlayer(ZerorateHlsPlayerOptions optionsOriginal) {
final options = ZerorateHlsPlayerOptions(
appId: optionsOriginal.appId,
region: optionsOriginal.region,
grantType: optionsOriginal.grantType,
subscriberId: optionsOriginal.subscriberId,
authUrl: optionsOriginal.authUrl,
type: optionsOriginal.type,
src: optionsOriginal.src,
autoplay: optionsOriginal.autoplay,
mediaType: optionsOriginal.mediaType,
poster: optionsOriginal.poster,
muted: optionsOriginal.muted,
aspectRatio: optionsOriginal.aspectRatio,
placeholder: optionsOriginal.placeholder,
placeholderOnTop: optionsOriginal.placeholderOnTop,
audioControlsConfiguration: optionsOriginal.audioControlsConfiguration,
videoControlsConfig: optionsOriginal.videoControlsConfig,
iconsColor: Colors.purple,
eventListeners: {
ZerorateHlsPlayerEventType.play: _handleEvent,
ZerorateHlsPlayerEventType.pause: _handleEvent,
ZerorateHlsPlayerEventType.seeked: _handleEvent,
ZerorateHlsPlayerEventType.bufferingStart: _handleEvent,
ZerorateHlsPlayerEventType.bufferingEnd: _handleEvent,
ZerorateHlsPlayerEventType.error: _handleEvent,
ZerorateHlsPlayerEventType.loaded: _handleEvent,
ZerorateHlsPlayerEventType.positionChanged: _handleEvent,
ZerorateHlsPlayerEventType.completed: _handleEvent,
ZerorateHlsPlayerEventType.thumbnailGenerated: _handleEvent,
},
errorBuilder: (error) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, color: Colors.red, size: 48),
const SizedBox(height: 16),
Text(
'An error occurred: ${error.toString()}',
style: const TextStyle(color: Colors.red),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => _initializePlayer(),
child: const Text('Retry'),
),
],
),
),
);
setState(() {
_player = ZerorateHlsPlayer(
options: options,
);
_eventLog.clear();
});
}
void _handleEvent(ZerorateHlsPlayerEvent event) {
if (event.type == ZerorateHlsPlayerEventType.positionChanged &&
_eventLog.length > 10) {
return;
}
setState(() {
String eventData = '';
if (event.data != null) {
if (event.data is Duration) {
eventData = _formatDuration(event.data);
} else if (event.data is Map) {
eventData = event.data.toString();
} else {
eventData = event.data.toString();
}
}
_eventLog.add('${event.type}: $eventData');
if (_eventLog.length > 100) {
_eventLog = _eventLog.sublist(_eventLog.length - 100);
}
});
}
String _formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, '0');
final minutes = twoDigits(duration.inMinutes.remainder(60));
final seconds = twoDigits(duration.inSeconds.remainder(60));
return '$minutes:$seconds';
}
void _initializePlayer() {
// _updatePlayer(PlayerConfig.defaultControlsVideo());
_updatePlayer(PlayerConfig.liveVideo());
}
@override
Widget build(BuildContext context) {
final isAudio = _player?.options.type == MediaType.audio;
Widget buildPlayerControls() {
final isAudio = _player?.options.type == MediaType.audio;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
children: [
// Playback controls
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: const Icon(Icons.play_arrow),
onPressed: () => _player?.play(),
tooltip: 'Play',
),
IconButton(
icon: const Icon(Icons.pause),
onPressed: () => _player?.pause(),
tooltip: 'Pause',
),
IconButton(
icon: const Icon(Icons.replay_10),
onPressed: () {
final position = _player?.currentPosition;
if (position != null) {
_player?.seekTo(
Duration(seconds: position.inSeconds - 10),
);
}
},
tooltip: 'Rewind 10s',
),
IconButton(
icon: const Icon(Icons.forward_10),
onPressed: () {
final position = _player?.currentPosition;
if (position != null) {
_player?.seekTo(
Duration(seconds: position.inSeconds + 10),
);
}
},
tooltip: 'Forward 10s',
),
],
),
// Audio-specific controls
if (isAudio)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Playback Speed:'),
const SizedBox(width: 8),
for (final speed in [0.5, 1.0, 1.5, 2.0])
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: ElevatedButton(
onPressed: () => _player?.setPlaybackSpeed(speed),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8),
),
child: Text('${speed}x'),
),
),
],
),
],
),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('Flutterhls Player Test'),
),
body: Column(
children: [
if (_player != null)
SafeArea(
child: AspectRatio(
aspectRatio: _player!.options.aspectRatio ?? 16 / 9,
child: _player!,
),
)
else
const SizedBox(
height: 40,
child: Center(child: CircularProgressIndicator()),
),
if ((_player!.options.type == MediaType.video &&
_player!.options.videoControlsConfig?.useDefaultControls ==
false) ||
(_player!.options.type == MediaType.audio &&
_player!.options.audioControlsConfiguration
?.useDefaultControls ==
false))
buildPlayerControls(),
Expanded(
child: DefaultTabController(
length: 2,
child: Column(
children: [
const TabBar(
tabs: [
Tab(text: 'Controls'),
Tab(text: 'Event Log'),
],
),
Expanded(
child: TabBarView(
children: [
SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 20),
if (isAudio) ...[
ElevatedButton(
onPressed: () => _updatePlayer(
PlayerConfig.defaultControlsAudio()),
child: const Text('Default Audio Controls'),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () => _updatePlayer(
PlayerConfig.customAudioControls()),
child: const Text('Custom Audio Controls'),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () => _updatePlayer(
PlayerConfig.fullyCustomAudioControls()),
child: const Text('Fully Custom Audio UI'),
),
ElevatedButton(
onPressed: () => _updatePlayer(
PlayerConfig.audioWithSpeedControl()),
child: const Text('Audio with Speed Control'),
),
] else ...[
ElevatedButton(
onPressed: () => _updatePlayer(
PlayerConfig.defaultControlsVideo()),
child: const Text('Default Video Controls'),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () => _updatePlayer(
PlayerConfig.onDemandVideo()),
child: const Text('On-Demand Video'),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () =>
_updatePlayer(PlayerConfig.liveVideo()),
child: const Text('Live Video'),
),
ElevatedButton(
onPressed: () => _updatePlayer(
PlayerConfig.externalControlsVideo()),
child: const Text('External Controls'),
),
],
// status information
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Media Type: ${_player?.options.type}'),
Text(
'Stream Type: ${_player?.options.mediaType}'),
Text(
'Autoplay: ${_player?.options.autoplay}'),
Text('Muted: ${_player?.options.muted}'),
if (_player?.options.type ==
MediaType.video &&
_player?.options.videoControlsConfig !=
null)
Text(
'Seek Preview Enabled: ${_player?.options.videoControlsConfig?.enableSeekPreview}'),
],
),
),
ExpansionTile(
title: const Text('Device Information'),
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SelectableText(_deviceInfo),
),
),
],
),
],
),
),
ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: _eventLog.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
_eventLog[index],
style: const TextStyle(fontSize: 12),
),
);
},
),
],
),
),
],
),
),
),
],
),
);
}
@override
void dispose() {
super.dispose();
}
}