A lightweight flutter package to simplify the creation of a miniplayer.

Related tags

Media miniplayer
Overview

Pub

A lightweight flutter package to simplify the creation of a miniplayer by providing a builder function with the current height and percentage progress. The widget responds to tap and drag gestures and is highly customizable. What is a miniplayer? Miniplayers are commonly used in media applications like Spotify and Youtube. A miniplayer can be expanded and minified and remains on the screen when minified until dismissed by the user. See the demo below for an example.

Tutorial: https://www.youtube.com/watch?v=umhl2hakkcY

Demo

demo

Usage

Stack(
  children: <Widget>[
    YourApp(),
    Miniplayer(
      minHeight: 70,
      maxHeight: 370,
      builder: (height, percentage) {
        return Center(
          child: Text('$height, $percentage'),
        );
      },
    ),
  ],
),

Options

Parameter Implementation Example
onDismiss
Miniplayer(
   onDismiss: () {
      //Handle onDismissed here
   }, 
),
      

If onDismiss is set, the miniplayer can be dismissed

valueNotifier
final ValueNotifier<double> playerExpandProgress =
    ValueNotifier(playerMinHeight);
    
Miniplayer(
   valueNotifier: playerExpandProgress, 
),
        

Allows you to use a global ValueNotifier with the current progress. This can be used to hide the BottomNavigationBar.

controller
final MiniplayerController controller = MiniplayerController();
    
Miniplayer(
   controller: controller, 
),
  
controller.animateToHeight(state: PanelState.MAX);
        

Persistence

Implementing the miniplayer as described under usage - for instance by wrapping it inside a Stack in the Scaffold body - would work out of the box but has some disadvantages. If you push a new screen via Navigator.push the miniplayer would disappear. What we want is a persistent miniplayer which stays on the screen.

If you want to archive persistency, you have the choice between two embedding options, which depends on your use case. The first method is only recommended for simple apps. If you want to use dialogs or other persistent widgets such as a BottomNavigationBar, the second (slightly more advanced) method is the right fit for you.

First method (Simple)

Using a Stack in the builder method

import 'package:flutter/material.dart';
import 'package:miniplayer/miniplayer.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Miniplayer example',
      theme: ThemeData(
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
      builder: (context, child) { // <--- Important part
        return Stack(
          children: [
            child,
            Miniplayer(
              minHeight: 70,
              maxHeight: 370,
              builder: (height, percentage) {
                if(percentage > 0.2)
                  //return Text('!mini');
                else 
                  //return Text('mini');
              },
            ),
          ],
        );
      },
    );
  }
}

Second method (Advanced)

Using a Stack in combination with a custom Navigator

import 'package:flutter/material.dart';
import 'package:miniplayer/miniplayer.dart';

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

final _navigatorKey = GlobalKey();

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Miniplayer example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Color(0xFFFAFAFA),
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MiniplayerWillPopScope(
      onWillPop: () async {
        final NavigatorState navigator = _navigatorKey.currentState;
        if (!navigator.canPop()) return true;
        navigator.pop();

        return false;
      },
      child: Scaffold(
        body: Stack(
          children: <Widget>[
            Navigator(
              key: _navigatorKey,
              onGenerateRoute: (RouteSettings settings) => MaterialPageRoute(
                settings: settings,
                builder: (BuildContext context) => FirstScreen(),
              ),
            ),
            Miniplayer(
              minHeight: 70,
              maxHeight: 370,
              builder: (height, percentage) => Center(
                child: Text('$height, $percentage'),
              ),
            ),
          ],
        ),
        bottomNavigationBar: BottomNavigationBar(
          currentIndex: 0,
          fixedColor: Colors.blue,
          items: [
            BottomNavigationBarItem(
              icon: Icon(Icons.home),
              label: 'Home',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.mail),
              label: 'Messages',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.person),
              label: 'Profile',
            )
          ],
        ),
      ),
    );
  }
}

class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Demo: FirstScreen')),
      body: Container(
        constraints: BoxConstraints.expand(),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () => Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => SecondScreen()),
              ),
              child: const Text('Open SecondScreen'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.of(context, rootNavigator: true).push(
                MaterialPageRoute(builder: (context) => ThirdScreen()),
              ),
              child: const Text('Open ThirdScreen with root Navigator'),
            ),
          ],
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Demo: SecondScreen')),
      body: Center(child: Text('SecondScreen')),
    );
  }
}

class ThirdScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Demo: ThirdScreen')),
      body: Center(child: Text('ThirdScreen')),
    );
  }
}

Roadmap

  • Provide better examples
  • Add an option to handle horizontal gestures as well (like Spotify does)
  • Rewrite the API for onDismiss (breaking change)
    • Marked onDismiss ad deprecated
Comments
  • Ability to go from expanded to min size on back button

    Ability to go from expanded to min size on back button

    The usual behaviour when pressing the back button is to minimise the bottom sheet, not going to the previous screen or exiting the app (i.e., YouTube Music, Spotify or Google Maps).

    Is it possible to get this? Thanks!

    opened by alberduris 11
  • Option to set initial snap position of the Miniplayer

    Option to set initial snap position of the Miniplayer

    Instead of having always start from min snap position, there should be an option to customise initial snap position. That can be anything in between max and min.

    opened by vishal-akg 5
  • Do you have an example code to use the MiniplayerController?

    Do you have an example code to use the MiniplayerController?

    Do you have an example code to use the MiniplayerController? I saw your update in the repository but it was called on null when I use likes this

                Miniplayer(
                  minHeight: 70,
                  maxHeight: 370,
                  builder: (height, percentage) {
                    return Center(
                      child: Text('$height, $percentage'),
                    );
                  },
                  onDismiss: () {
                    //Handle onDismissed here
                  },
                  controller: controller,
                ),
    
                RaisedButton(
                  onPressed: () {
                    // print(controller);
                    controller.animateToHeight(state: PanelState.MAX); // **when i click this**
                  },
                  child: const Text('1', style: TextStyle(fontSize: 20)),
                )
    
    The method 'animateToHeight' was called on null.
    Receiver: null
    Tried calling: animateToHeight(state: Instance of 'PanelState')
    
    
    opened by hatsadin09 3
  • Transparent background

    Transparent background

    Hello,

    Thank you for this very efficient plugin! I would like to be able to customize it more and make the background color transparent or blurred, is it possible? Despite several days of testing, I never managed to do it.

    opened by dmckminard 2
  • Widget in miniplayer refreshes which causes an error.

    Widget in miniplayer refreshes which causes an error.

    I asked the same questionon stackoverflow and after that found a solution. The problem is that miniplayer rebuilds its child widget when you start to dismiss it because of this line: (value == 0) return child!;

    This causes an error when for example using video_player. Because at the time you start dismissing the miniplayer the video is playing however then miniplayer rebuilds its child and the video_player gets black (or starts playing over from the start).

    If you just delete the line I mentioned above nothing will change and the error will disappear.

    opened by eiler21 2
  • Animate image and controls

    Animate image and controls

    Hi, I was hoping that in the example you would have shown how you animated the audio image and the player controls to scale with the player. Do you have an example that shows the animation, otherwise, can you give a simple explanation on how you accomplished it?

    opened by frazras 2
  • How to open the miniplayer with maxHeight by default ?

    How to open the miniplayer with maxHeight by default ?

    First of all, great plugin and exactly I was looking for :)

    I have a requirement of whenever I click on a button in a page, I need to open the mini-player in full height? Any idea how to achieve this?

    Current behavior is...when I click on it by default it is showing the mini-player with the minimum height. Thanks for your help,

    static const double _playerMinHeight = 70.0;

    // using Getx for the state management Obx( () => !_videoController.isSessionInProgress.value ? const SizedBox.shrink() : Miniplayer( controller: _videoController.miniPlayerController, minHeight: _playerMinHeight, maxHeight: MediaQuery.of(context).size.height, builder: (height, percentage) { if (percentage > 0.10) return VideoView(); else return Text('mini'); }), ),

    opened by toklive 1
  • Bug: doesn't adhere to darktheme

    Bug: doesn't adhere to darktheme

    Hi,

    just tested this package with a device with system dark theme. However, it doesn't change the background color of the player, it seems to be hard coded to white.

    Fix: in miniplayer.dart https://github.com/peterscodee/miniplayer/blob/4fa9d07e59d74c0a566ccdb6469fa1338b417c38/lib/miniplayer.dart#L206

    Please consider changing: color: Colors.white,

    to

    color: Theme.of(context).canvasColor

    Thanks.

    opened by MagnusJohansson 1
  • MiniController animation functionally won't work after parent widget rebuilds.

    MiniController animation functionally won't work after parent widget rebuilds.

    As listener is attached to Minicontroller inside initState widget life cycle method but Mini player is very likely to be recreated because of parent widget rebuilds ex. Safe Area change ( Device Navigation keys hide, show) , MediaQuery and others so. Opacity test 0 <= opacity <= 1.0 fails too because after safe area height changes.

    opened by vishal-akg 1
  • How to use the better player package in this package?

    How to use the better player package in this package?

    I have designed an application in which we have a movie player that has used the better player package and I want the player display to continue at the bottom of the page when the back button is pressed. Similar to YouTube, but when I use this package to resolve this issue, I get an error A VideoPlayerController was used after being disposed. This error appears when the player is minimized and I make it full screen.

    my code is:

    class MyHomePage extends StatefulWidget {
      MyHomePage({Key? key}) : super(key: key);
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      BetterPlayerController? _betterPlayerController;
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        BetterPlayerDataSource betterPlayerDataSource = BetterPlayerDataSource(
            BetterPlayerDataSourceType.network,
            "https://cdn.hobyna.app/media/videos/2022-04-17/283108507127672349878.mp4");
        _betterPlayerController = BetterPlayerController(
            const BetterPlayerConfiguration(),
            betterPlayerDataSource: betterPlayerDataSource);
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Stack(
            children: [
              Miniplayer(
                minHeight: 150,
                maxHeight: MediaQuery.of(context).size.height,
                builder: (h, i) {
                  final bool miniplayer = i < miniplayerPercentageDeclaration;
                  if (!miniplayer) {
                    return AspectRatio(
                      aspectRatio: 16 / 9,
                      child: BetterPlayer(
                        controller: _betterPlayerController!,
                      ),
                    );
                  } else {
                    return SizedBox(
                      height: 150.0,
                      width: 100.0,
                      child: AspectRatio(
                        aspectRatio: 16 / 9,
                        child: BetterPlayer(
                          controller: _betterPlayerController!,
                        ),
                      ),
                    );
                  }
                },
              )
            ],
          ),
        );
      }
    }
    opened by AhmadFalahian 0
  • How to initialize and dispose states of miniplayer?

    How to initialize and dispose states of miniplayer?

    I'd like to make a phone call via WebRTC in miniplayer. Does anybody know how to initialize RTC when miniplayer is shown, and dispose RTC when miniplayer isn't shown? I'm using Offstage to show/hide miniplayer in Stack.

    opened by nwatab 0
  • Expanded view dismisses on any tap

    Expanded view dismisses on any tap

    I love using the package! I'm implementing a YouTube like player and everything works perfectly, however when view is expanded it gets dismissed on a tap on any part of the view. I'm not using the onDismissed or onDismiss property. I tried examining the native code to attempt to make changes but was unsuccessful.

    bug 
    opened by zeddyyz 1
  • How to hide bottomnavigationbar when miniplayer at max height

    How to hide bottomnavigationbar when miniplayer at max height

    I saw this comment for this property... but not sure how to use it ? My app is using Getx

    ///Allows you to use a global ValueNotifier with the current progress. ///This can be used to hide the BottomNavigationBar. final ValueNotifier? valueNotifier;

    I got this error https://stackoverflow.com/questions/68073121/getx-and-hide-bottomnavigation-bar-error-with-stateful-widget-rendering, when I tried to make it work using Getx

    question 
    opened by toklive 5
Flutter video trimmer package

A Flutter package for trimming videos Features Customizable video trimmer Video playback control Retrieving and storing video file Also, supports conv

Souvik Biswas 349 Jan 3, 2023
A flutter package for iOS and Android for applying filter to an image

Photo Filters package for flutter A flutter package for iOS and Android for applying filter to an image. A set of preset filters are also available. Y

Ansh rathod 1 Oct 26, 2021
Flutter package for creating a fully customizable and editable image widget.

EditableImage Flutter Package Flutter package for creating a fully customizable and editable image widget. The package has been written solely in Dart

Bulent Baris Kilic 5 Jun 13, 2022
A small image utils package for flutter written in dart.

flutter_simple_image_utils A small image utils package for flutter written in dart. Usage: import 'package:flutter_simple_image_utils/flutter_simple_i

Hamlet D'Arcy 1 Nov 18, 2021
A Flutter package for both android and iOS which provides Audio recorder

social_media_recorder A Flutter package for both android and iOS which provides

subhikhalifeh 16 Dec 29, 2022
Audio classification Tflite package for flutter (iOS & Android).

Audio classification Tflite package for flutter (iOS & Android). Can also support Google Teachable Machine models.

Michael Nguyen 47 Dec 1, 2022
BC Image Editor - Flutter package for image editing

BC Image Editor You can edit image using this package and also you can create flex preview image by setting foreground to null. For now, you can use o

Berkay CEYLAN 9 Nov 21, 2022
This is a flutter package of video player. it's a very simple and easy to use.

This is a flutter package of video player. it's a very simple and easy to use.

初冬 184 Nov 18, 2022
Enhanced pub package commands.

Enhanced pub package commands. Add latest resolvable package Remove dependency View package info Like/unlike/view liked pub.dev packages Improved conf

Leo Farias 14 Oct 19, 2022
Collection of extension function of just_audio package for auto-handle caching

just_audio_cache Collection of extension function of just_audio package for auto-handle caching audio files How to use The premise is you already have

Yoda 10 Aug 24, 2022
Image Extensions A wrapper library for image package with some extra functions.

A wrapper library for image package with some extra functions. Installation Add this to your package's pubspec.yaml file: dependencie

Vanxh 1 Jan 15, 2022
Dart package for reading and writing wav files

wav Simple tools for reading and writing WAV files. Written in pure Dart, with no dependencies. This package currently supports reading and writing 8/

Liam Appelbe 8 Dec 28, 2022
Official Flutter SDK for LiveKit. Easily add real-time video and audio to your Flutter apps.

LiveKit Flutter SDK Official Flutter SDK for LiveKit. Easily add real-time video and audio to your Flutter apps. This package is published to pub.dev

LiveKit 116 Dec 14, 2022
Flutter Music Player - A complete and open source music player designed in flutter.

Flutter Music Player A complete and open source music player designed in flutter. It is first complete music player designed in flutter. This app expl

Nabraj Khadka 3 Aug 20, 2022
Flutter plugin for use Video.js in flutter web

Flutter Video.js player Flutter plugin for use Video.js in flutter web Installation Add it to your package's pubspec.yaml file dependencies: video_j

null 15 Oct 17, 2022
Audio player app in Flutter. Created as a tutorial for learning Flutter.

Music Player: create a simple Flutter music player app This is a Flutter project used during a series of articles on I should go to sleep. License Cop

Michele Volpato 11 May 5, 2022
This Flutter plugin created to show how to use OpenCV and ZXing C++ libraries natively in Flutter with Dart FFI using the camera stream

OpenCV and ZXing C++ libraries natively in Flutter with Dart FFI using the camera stream

Khoren Markosyan 17 Oct 21, 2022
Flutter radio player mod flutter 2.5

A Flutter plugin to play streaming audio content with background support and lock screen controls.

Ayotunde abdulsalam 1 Mar 14, 2022
Virlow Flutter Recorder - an open-source Flutter application that can transcribe recorded audio

The Virlow Flutter Recorder is an open-source Flutter application that can transcribe recorded audio, plus it includes TL;DR and Short Hand Notes for your transcription. It also consists of a rich text editor that allows you to edit the transcription plus add any additional notes you require.

null 12 Dec 26, 2022