flutter_playout 1.0.5

  • Readme
  • Changelog
  • Example
  • Installing
  • new68

flutter_playout #

Audio & Video player in Flutter. This plugin provides audio/video playback with background audio support and lock screen controls for both iOS & Android.

  • Video only supports HLS at the moment for both iOS & Android.

  • Audio supports playback from URL only.

iOS Example #

screenshot1screenshot4screenshot3

Android Example #

screenshot5screenshot6

Getting Started #

Android #

Uses ExoPlayer with PlatformView for Video playback and MediaPlayer for audio playback.

When using this plugin, please make sure you have included a notification icon for your project in drawable resource directory named ic_notification_icon. This plugin will use this icon to show lock screen controls for playback.

iOS #

Uses AVPlayer with PlatformView for video playback and AVPlayer with Flutter MethodChannels for audio playback.

Please make sure you've enabled background audio capability for your project. Please also note that the player might not function properly on a simulator.

Opt-in to the embedded views preview by adding a boolean property to the app's Info.plist file with the key io.flutter.embedded_views_preview and the value YES.

1.0.5 [October 12, 2019]

  • Fixed an issue causing iOS plugin to not respond to dispose

1.0.4 [October 12, 2019]

  • Updated iOS plugin to use Swift 5 compiler

1.0.3 [October 12, 2019]

  • Implemented video playback for Android
  • Fixed an issue with lock screen controls where subtitle wasn't being displayed correctly

1.0.2 [October 9, 2019]

  • Implemented video playback for iOS

1.0.1 #

  • Updated documentation to include example implementation for the plugin

1.0.0 #

  • Play audio for both iOS & Android
  • Play audio in background with lock screen controls for both iOS & Android

example/README.md

How to use flutter_playout #

Below is an example app showcasing both video and audio players from this plugin.

main.dart #

import 'package:flutter/material.dart';

import 'package:flutter_playout_example/audio.dart';
import 'package:flutter_playout_example/video.dart';

void main() => runApp(PlayoutExample());

class PlayoutExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: "AV Playout",
      home: Scaffold(
        backgroundColor: Colors.black,
        appBar: AppBar(
          brightness: Brightness.dark,
          backgroundColor: Colors.grey[900],
          centerTitle: true,
          leading: IconButton(
            icon: Icon(Icons.menu),
            onPressed: () {},
          ),
          actions: <Widget>[
            IconButton(
              icon: Icon(Icons.favorite),
              onPressed: () {},
            )
          ],
          title: Row(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Icon(
                Icons.local_play,
                color: Colors.white,
              ),
              Container(
                width: 7.0,
              ),
              Text(
                "AV Player",
                style: Theme.of(context)
                    .textTheme
                    .title
                    .copyWith(color: Colors.white),
              )
            ],
          ),
        ),
        body: Container(
          color: Colors.black,
          child: CustomScrollView(
            slivers: <Widget>[
              SliverToBoxAdapter(
                child: Container(
                  padding: EdgeInsets.fromLTRB(17.0, 33.0, 17.0, 0.0),
                  child: Text(
                    "Video Player",
                    style: Theme.of(context).textTheme.display1.copyWith(
                        color: Colors.pink[500], fontWeight: FontWeight.w600),
                  ),
                ),
              ),
              SliverToBoxAdapter(
                child: Container(
                  padding: EdgeInsets.fromLTRB(17.0, 0.0, 17.0, 30.0),
                  child: Text(
                    "Plays video from a URL with background audio support and lock screen controls.",
                    style: Theme.of(context).textTheme.subhead.copyWith(
                        color: Colors.white70, fontWeight: FontWeight.w400),
                  ),
                ),
              ),
              SliverToBoxAdapter(
                child: VideoPlayout(),
              ),
              SliverToBoxAdapter(
                child: Container(
                  padding: EdgeInsets.fromLTRB(17.0, 23.0, 17.0, 0.0),
                  child: Text(
                    "Audio Player",
                    style: Theme.of(context).textTheme.display1.copyWith(
                        color: Colors.pink[500], fontWeight: FontWeight.w600),
                  ),
                ),
              ),
              SliverToBoxAdapter(
                child: Container(
                  padding: EdgeInsets.fromLTRB(17.0, 0.0, 17.0, 30.0),
                  child: Text(
                    "Plays audio from a URL with background audio support and lock screen controls.",
                    style: Theme.of(context).textTheme.subhead.copyWith(
                        color: Colors.white70, fontWeight: FontWeight.w400),
                  ),
                ),
              ),
              SliverToBoxAdapter(
                child: AudioPlayout(),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Video Playout #

package:flutter_playout_example/video.dart #

import 'package:flutter/material.dart';
import 'package:flutter_playout/player_observer.dart';
import 'package:flutter_playout/video.dart';

class VideoPlayout extends StatelessWidget with PlayerObserver {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: AspectRatio(
        aspectRatio: 16 / 9,
        child: Video(
          autoPlay: false,
          title: "MTA International",
          subtitle: "Reaching The Corners Of The Earth",
          isLiveStream: false,
          url: "https://your_video_stream.com/stream_test.m3u8",
          onViewCreated: _onViewCreated,
        ),
      ),
    );
  }

  void _onViewCreated(int viewId) {
    listenForVideoPlayerEvents(viewId);
  }

  @override
  void onPlay() {
    // TODO: implement m_onPlay
    super.onPlay();
  }

  @override
  void onPause() {
    // TODO: implement m_onPause
    super.onPause();
  }

  @override
  void onComplete() {
    // TODO: implement m_onComplete
    super.onComplete();
  }

  @override
  void onTime(int position) {
    // TODO: implement m_onTime
    super.onTime(position);
  }

  @override
  void onSeek(int position, double offset) {
    // TODO: implement m_onSeek
    super.onSeek(position, offset);
  }

  @override
  void onError(String error) {
    // TODO: implement m_onError
    super.onError(error);
  }
}

Audio Playout #

package:flutter_playout_example/audio.dart #

import 'package:flutter/material.dart';
import 'package:flutter_playout/audio.dart';
import 'package:flutter_playout/player_observer.dart';
import 'package:flutter_playout/player_state.dart';

class AudioPlayout extends StatefulWidget {
  // Audio url to play
  final String url = "https://your_audio_stream.com/stream_test.mp3";

  // Audio track title. this will also be displayed in lock screen controls
  final String title = "MTA International";

  // Audio track subtitle. this will also be displayed in lock screen controls
  final String subtitle = "Reaching The Corners Of The Earth";

  // Audio duration in milliseconds
  final int duration = 1604277;

  @override
  _AudioPlayout createState() => _AudioPlayout();
}

class _AudioPlayout extends State<AudioPlayout> with PlayerObserver {
  Audio _audioPlayer;
  PlayerState audioPlayerState = PlayerState.STOPPED;

  Duration duration = Duration(milliseconds: 1);
  Duration currentPlaybackPosition = Duration.zero;

  get isPlaying => audioPlayerState == PlayerState.PLAYING;
  get isPaused =>
      audioPlayerState == PlayerState.PAUSED ||
      audioPlayerState == PlayerState.STOPPED;

  get durationText =>
      duration != null ? duration.toString().split('.').first : '';
  get positionText => currentPlaybackPosition != null
      ? currentPlaybackPosition.toString().split('.').first
      : '';

  @override
  void initState() {
    super.initState();

    // Set track duration
    duration = Duration(milliseconds: widget.duration);

    // Init audio player with a callback to handle events
    _audioPlayer = new Audio();

    // Listen for audio player events
    listenForAudioPlayerEvents();
  }

  @override
  void onPlay() {
    print("onPlay");
    setState(() {
      audioPlayerState = PlayerState.PLAYING;
    });
    super.onPlay();
  }

  @override
  void onPause() {
    print("onPause");
    setState(() {
      audioPlayerState = PlayerState.PAUSED;
    });
    super.onPause();
  }

  @override
  void onComplete() {
    print("onComplete");
    setState(() {
      audioPlayerState = PlayerState.PAUSED;
    });
    super.onComplete();
  }

  @override
  void onTime(int position) {
    print("onTime $position");
    setState(() {
      currentPlaybackPosition = Duration(seconds: position);
    });

    /* reset on playback end */
    if (currentPlaybackPosition.inSeconds > 0 &&
        currentPlaybackPosition.inSeconds >= duration.inSeconds) {
      stop();
    }

    super.onTime(position);
  }

  @override
  void onSeek(int position, double offset) {
    print("onSeek $position $offset");
    super.onSeek(position, offset);
  }

  @override
  void onError(String error) {
    print("onError $error");
    super.onError(error);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey[900],
      child: _buildPlayerControls(),
    );
  }

  Widget _buildPlayerControls() {
    return Container(
      color: Colors.transparent,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.max,
        children: <Widget>[
          Row(
            children: <Widget>[
              IconButton(
                splashColor: Colors.transparent,
                icon: Icon(
                  isPlaying
                      ? Icons.pause_circle_filled
                      : Icons.play_circle_filled,
                  color: Colors.white,
                  size: 50,
                ),
                onPressed: () {
                  if (isPlaying) {
                    pause();
                  } else {
                    play();
                  }
                },
              ),
              Column(
                mainAxisAlignment: MainAxisAlignment.start,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Container(
                    padding: EdgeInsets.fromLTRB(20.0, 11.0, 5.0, 3.0),
                    child: Text(widget.title,
                        style:
                            TextStyle(fontSize: 11, color: Colors.grey[100])),
                  ),
                  Container(
                    padding: EdgeInsets.fromLTRB(20.0, 0.0, 5.0, 0.0),
                    child: Text(widget.subtitle,
                        style: TextStyle(fontSize: 19, color: Colors.white)),
                  ),
                ],
              )
            ],
          ),
          Container(
            height: 15.0,
          ),
          Slider(
            activeColor: Colors.white,
            value: currentPlaybackPosition?.inMilliseconds?.toDouble() ?? 0.0,
            onChanged: (double value) {
              setState(() {
                currentPlaybackPosition = Duration(milliseconds: value.toInt());
              });
              seekTo(value / 1000);
            },
            min: 0.0,
            max: duration.inMilliseconds.toDouble(),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              Container(),
              Container(
                padding: EdgeInsets.fromLTRB(20, 5, 20, 10),
                child: Text(
                  _playbackPositionString(),
                  style: Theme.of(context)
                      .textTheme
                      .body1
                      .copyWith(color: Colors.white),
                ),
              ),
            ],
          )
        ],
      ),
    );
  }

  String _playbackPositionString() {
    var currentPosition = Duration(
        milliseconds:
            duration.inMilliseconds - currentPlaybackPosition.inMilliseconds);

    return currentPosition.toString().split('.').first;
  }

  // Request audio play
  Future<void> play() async {
    // here we send position in case user has scrubbed already before hitting
    // play in which case we want playback to start from where user has
    // requested
    _audioPlayer.play(widget.url,
        title: widget.title,
        subtitle: widget.subtitle,
        position: currentPlaybackPosition,
        isLiveStream: true);
    setState(() {
      audioPlayerState = PlayerState.PLAYING;
    });
  }

  // Request audio pause
  Future<void> pause() async {
    _audioPlayer.pause();
    setState(() => audioPlayerState = PlayerState.PAUSED);
  }

  // Request audio stop. this will also clear lock screen controls
  Future<void> stop() async {
    _audioPlayer.stop();

    setState(() {
      audioPlayerState = PlayerState.STOPPED;
      currentPlaybackPosition = Duration.zero;
    });
  }

  // Seek to a point in seconds
  Future<void> seekTo(double seconds) async {
    _audioPlayer.seekTo(seconds);
  }

  @override
  void dispose() {
    _audioPlayer.dispose();
    super.dispose();
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  flutter_playout: ^1.0.5

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:flutter_playout/audio.dart';
import 'package:flutter_playout/player_observer.dart';
import 'package:flutter_playout/player_state.dart';
import 'package:flutter_playout/video.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
36
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
68
Learn more about scoring.

We analyzed this package on Oct 16, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.5.1
  • pana: 0.12.21
  • Flutter: 1.9.1+hotfix.4

Platforms

Detected platforms: Flutter

References Flutter, and has no conflicting libraries.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11 1.14.12
meta 1.1.7
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test