Skip to content

Instantly share code, notes, and snippets.

@nhancv
Last active July 3, 2022 11:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nhancv/4c1a21d22244c1c42ec96b0738841a16 to your computer and use it in GitHub Desktop.
Save nhancv/4c1a21d22244c1c42ec96b0738841a16 to your computer and use it in GitHub Desktop.
Flutter stream hls m3u8

build.gradle

    aaptOptions {
        noCompress '.flv', '.mp4'
    }

    signingConfigs {}

AndroidManifest.xml

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:requestLegacyExternalStorage="true"
        >

pubspec.yaml

  fijkplayer: ^0.8.7
  permission_handler: ^5.1.0+2

request storage permission

Permission.storage.request();
import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/material.dart';
import 'package:nft/services/safety/base_stateful.dart';
import 'package:nft/utils/app_extension.dart';
import 'package:nft/utils/app_style.dart';
/*
/// PARENT'S WIDGET
/// Controller, state
final WM3u8Controller wm3u8controller = WM3u8Controller();
WM3u8State wm3u8state;
@override
Future<void> afterFirstBuild(BuildContext context) async {
super.afterFirstBuild(context);
wm3u8controller.onStateChange = (WM3u8State state) {
print('onStateChange: $state');
setState(() {
wm3u8state = state;
});
};
}
/// BUILD
/// Stream container
WM3u8Container(
streamUrl: streamUrl,
controller: wm3u8controller,
),
/// Refesh button
if (wm3u8state == WM3u8State.error ||
wm3u8state == WM3u8State.completed)
Container(
alignment: Alignment.center,
margin: EdgeInsets.only(top: 95.H),
child: IconButton(
icon: const Icon(
Icons.refresh,
color: Colors.white,
),
onPressed: () {
wm3u8controller.onRestart();
}),
)
*/
enum WM3u8State { loading, started, paused, error, completed }
class WM3u8Controller {
WM3u8Controller();
// Implement in parent's widget
Function(WM3u8State value) onStateChange = (_) {};
// Implement in widget
Function() onRestart;
FijkPlayer player;
}
class WM3u8Container extends StatefulWidget {
const WM3u8Container({Key key, this.controller, this.streamUrl})
: super(key: key);
final String streamUrl;
final WM3u8Controller controller;
@override
_WM3u8ContainerState createState() => _WM3u8ContainerState();
}
class _WM3u8ContainerState extends BaseStateful<WM3u8Container> {
WM3u8Controller get controller => widget.controller;
WM3u8State _state;
set state(WM3u8State value) {
controller.onStateChange(value);
setState(() {
_state = value;
});
}
@override
void initState() {
super.initState();
controller.onRestart = () {
state = WM3u8State.loading;
};
}
@override
void afterFirstBuild(BuildContext context) {
super.afterFirstBuild(context);
state = WM3u8State.loading;
}
@override
Widget build(BuildContext context) {
super.build(context);
return _state == null
? Container()
: Stack(
children: <Widget>[
if (_state != WM3u8State.error && _state != WM3u8State.completed)
_WM3u8(
streamUrl: widget.streamUrl,
onStarted: () {
state = WM3u8State.started;
},
onPaused: () {
state = WM3u8State.paused;
},
onError: () {
state = WM3u8State.error;
},
onCompleted: () {
state = WM3u8State.completed;
},
controller: controller,
),
if (_state == WM3u8State.error || _state == WM3u8State.completed)
Container(
margin: EdgeInsets.symmetric(horizontal: 60.W),
alignment: Alignment.center,
child: Text(
'THE STREAM IS NOT LIVE YET OR IT IS IDLE.\nWHEN IT\'S LIVE YOU\'LL SEE IT HERE',
textAlign: TextAlign.center,
style: normalTextStyle(20.SP,
color: Colors.white,
fontFamily: appTheme.assets.fontLeaguegothic),
)),
if (_state == WM3u8State.loading) _loadingView()
],
);
}
// Build loading
Widget _loadingView() {
return Container(
color: Colors.black12,
alignment: Alignment.center,
child: SizedBox(
height: 50.H,
width: 50.H,
child: const CircularProgressIndicator(
backgroundColor: Colors.white,
),
),
);
}
}
class _WM3u8 extends StatefulWidget {
const _WM3u8({
@required this.streamUrl,
this.onStarted,
this.onPaused,
this.onError,
this.onCompleted,
this.controller,
Key key,
}) : super(key: key);
final String streamUrl;
final Function onStarted;
final Function onPaused;
final Function onError;
final Function onCompleted;
final WM3u8Controller controller;
@override
_WM3u8State createState() => _WM3u8State();
}
class _WM3u8State extends BaseStateful<_WM3u8> {
WM3u8Controller get controller => widget.controller;
final FijkPlayer player = FijkPlayer();
@override
void initState() {
super.initState();
controller.player = player;
}
@override
void afterFirstBuild(BuildContext context) {
super.afterFirstBuild(context);
player.addListener(() {
final FijkState state = player.value.state;
print('updatestate: $state');
if (state == FijkState.error && widget.onError != null) {
widget.onError();
} else if (state == FijkState.started && widget.onStarted != null) {
widget.onStarted();
} else if (state == FijkState.paused && widget.onPaused != null) {
widget.onPaused();
} else if (state == FijkState.completed && widget.onCompleted != null) {
widget.onCompleted();
}
});
// Start player
// https://fijkplayer.befovy.com/docs/en/fijkstate.html#gsc.tab=0
player.setDataSource(widget.streamUrl, autoPlay: true);
}
@override
void dispose() {
player.release().then((_) {
player.dispose();
});
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
return FijkView(
player: player,
color: Colors.black12,
fit: (player.value?.size?.aspectRatio ?? 2) > 1
? FijkFit.contain
: FijkFit.cover,
);
}
}
import 'dart:math';
import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/material.dart';
import 'package:nft/services/safety/base_stateful.dart';
import 'package:nft/utils/app_extension.dart';
class WM3u8Panel extends StatefulWidget {
const WM3u8Panel({
@required this.player,
@required this.isPlaying,
});
final FijkPlayer player;
final bool isPlaying;
@override
_WM3u8PanelState createState() => _WM3u8PanelState();
}
class _WM3u8PanelState extends BaseStateful<WM3u8Panel> {
FijkPlayer get player => widget.player;
bool get isPlaying => widget.isPlaying ?? false;
double progressValueStart = 0;
double progressValue = 0;
bool isChangeProgress = false;
@override
Widget build(BuildContext context) {
super.build(context);
return player == null
? Container(color: Colors.transparent)
: Container(
height: 60.H,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
IconButton(
icon: Icon(
isPlaying
? Icons.pause_circle_filled
: Icons.play_circle_filled,
),
onPressed: () {
isPlaying ? player.pause() : player.start();
},
),
Expanded(
child: StreamBuilder<Duration>(
stream: player.onCurrentPosUpdate,
builder: (_, AsyncSnapshot<Duration> durationSnapshot) {
Duration duration = Duration.zero;
if (durationSnapshot.hasError ||
durationSnapshot.hasData) {
duration = durationSnapshot.data;
}
final double m3u8PlayerProgress =
duration.inMilliseconds.toDouble();
final bool isUpward = progressValueStart <= progressValue;
return FijkSlider(
colors: const FijkSliderColors(
bufferedColor: Colors.white54),
cacheValue: player.bufferPos.inMilliseconds.toDouble(),
max: max(
player.value.duration.inMilliseconds.toDouble(), 1),
value: (!isChangeProgress &&
(isUpward
? progressValue <= m3u8PlayerProgress
: progressValue > m3u8PlayerProgress))
? m3u8PlayerProgress
: progressValue,
onChanged: (double value) {
progressValue = value;
setState(() {});
},
onChangeStart: (double value) {
progressValue =
player.currentPos.inMilliseconds.toDouble();
progressValueStart = progressValue;
isChangeProgress = true;
setState(() {});
},
onChangeEnd: (double value) {
player.seekTo(value.toInt());
// Update
progressValue = value;
isChangeProgress = false;
setState(() {});
},
);
},
),
),
SizedBox(width: 15.W),
],
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment