Pen Settings

JavaScript

Babel includes JSX processing.

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

Flutter

              
                /// Implementing a media player UI with full-width seek slider which sits on the
/// border between the playlist and controls.
///
/// Positioning doesn't work properly in the web version for some reason.
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui';

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.deepOrange,
      ),
      home: MediaPlayer(),
    ),
  );
}

var _uiBackgroundColor = Color.fromRGBO(241, 241, 241, 1.0);

String formatTime(Duration duration) {
  String minutes = (duration.inMinutes % 60).toString().padLeft(2, '0');
  String seconds = (duration.inSeconds % 60).toString().padLeft(2, '0');
  return '${duration.inHours >= 1 ? duration.inHours.toString() + ':' : ''}$minutes:$seconds';
}

/// A custom track shape for a [Slider] which lets it go full-width.
///
/// From https://github.com/flutter/flutter/issues/37057#issuecomment-516048356
class FullWidthTrackShape extends RoundedRectSliderTrackShape {
  Rect getPreferredRect({
    @required RenderBox parentBox,
    Offset offset = Offset.zero,
    @required SliderThemeData sliderTheme,
    bool isEnabled = false,
    bool isDiscrete = false,
  }) {
    final double trackHeight = sliderTheme.trackHeight;
    final double trackLeft = offset.dx;
    final double trackTop =
        offset.dy + (parentBox.size.height - trackHeight) / 2;
    final double trackWidth = parentBox.size.width;
    return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
  }
}

class MediaPlayer extends StatefulWidget {
  MediaPlayer();

  @override
  State<StatefulWidget> createState() => _MediaPlayerState();
}

class _MediaPlayerState extends State<MediaPlayer> {
  Timer _ticker;
  String _state = 'stopped';
  Duration _time = Duration.zero;
  Duration _length = Duration(minutes: 12, seconds: 34);
  bool _draggingTime = false;
  bool _showTimeLeft = false;

  GlobalKey _controlsKey = GlobalKey();

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

  @override
  dispose() {
    if (_ticker.isActive) {
      _ticker.cancel();
    }
    super.dispose();
  }

  _startTicking() {
    _ticker = Timer.periodic(Duration(seconds: 1), _tick);
  }

  _stopTicking() {
    if (_ticker?.isActive == true) {
      _ticker.cancel();
      _ticker = null;
    }
  }

  _tick(timer) async {
    if (_draggingTime) {
      return;
    }
    setState(() {
      if (_time == _length) {
        _stop();
      } else {
        _time += Duration(seconds: 1);
      }
    });
  }

  String get _title => 'Media Player ($_state)';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: <Widget>[
            _buildTopBar(),
            Expanded(
              child: Stack(children: [
                Column(children: [
                  Expanded(child: _buildPlaylist()),
                  _buildControls(),
                ]),
                _buildSeekSlider(),
              ]),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildTopBar() {
    return Material(
      color: _uiBackgroundColor,
      child: ListTile(
        contentPadding: EdgeInsets.only(left: 14),
        title: Text(
          _title,
          overflow: TextOverflow.ellipsis,
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
      ),
    );
  }

  String get _backgroundUrl =>
      'https://assets.bigcartel.com/product_images/144347278/Valerie1000150.jpg';

  Widget _buildPlaylist() {
    return Stack(children: [
      // Cover art background
      Positioned.fill(
        child: Opacity(
          opacity: .1,
          child: ClipRect(
            child: ImageFiltered(
              imageFilter: ImageFilter.blur(
                sigmaX: 2,
                sigmaY: 2,
              ),
              child: FadeInImage(
                imageErrorBuilder: (_, __, ___) => SizedBox(),
                placeholder: MemoryImage(kTransparentImage),
                image: NetworkImage(_backgroundUrl),
                fit: BoxFit.cover,
              ),
            ),
          ),
        ),
      ),
      // Playlist items
      ListView()
    ]);
  }

  _toggleShowTimeLeft() {
    setState(() {
      _showTimeLeft = !_showTimeLeft;
    });
  }

  _playPause() {
    setState(() {
      _state = (_state == 'playing' ? 'paused' : 'playing');
    });
    if (_state == 'playing') {
      _startTicking();
    } else {
      _stopTicking();
    }
  }

  _stop() {
    setState(() {
      _state = 'stopped';
      _time = Duration.zero;
      _stopTicking();
    });
  }

  String get _timeElapsed => _state != 'stopped' ? formatTime(_time) : '––:––';

  String get _trackLength {
    if (_state == 'stopped' || _length == Duration.zero) {
      return '––:––';
    }
    return _showTimeLeft
        ? '-' + formatTime(_length - _time)
        : formatTime(_length);
  }

  Widget _buildControls() {
    return Material(
      key: _controlsKey,
      color: _uiBackgroundColor,
      child: Stack(
        children: [
          // Time elapsed / time left
          Padding(
            padding: const EdgeInsets.only(top: 16),
            child: Column(
              children: <Widget>[
                Padding(
                  padding: EdgeInsets.symmetric(horizontal: 16),
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      Text(_timeElapsed),
                      Spacer(),
                      GestureDetector(
                        onTap: _toggleShowTimeLeft,
                        child: Text(_trackLength),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
          // Playback controls
          Padding(
            padding: const EdgeInsets.only(top: 16),
            child: Row(
              children: <Widget>[
                Spacer(),
                IconButton(
                  iconSize: 36,
                  icon: Icon(
                    Icons.skip_previous,
                    color: Colors.black,
                  ),
                  onPressed: () {},
                ),
                // Play/pause button
                GestureDetector(
                  onLongPress: _stop,
                  child: TweenAnimationBuilder(
                    tween: Tween<double>(
                        begin: 0.0,
                        end: _state == 'paused' || _state == 'stopped'
                            ? 0.0
                            : 1.0),
                    duration: Duration(milliseconds: 250),
                    curve: Curves.easeInOut,
                    builder: (context, progress, child) => IconButton(
                      iconSize: 64,
                      onPressed: _playPause,
                      icon: AnimatedIcon(
                        icon: AnimatedIcons.play_pause,
                        color: Colors.black,
                        progress: AlwaysStoppedAnimation<double>(progress),
                      ),
                    ),
                  ),
                ),
                IconButton(
                  iconSize: 36,
                  icon: Icon(
                    Icons.skip_next,
                    color: Colors.black,
                  ),
                  onPressed: () {},
                ),
                Spacer(),
              ],
            ),
          ),
        ],
      ),
    );
  }

  double get _seekSliderValue {
    if (_length.inSeconds == 0) {
      return 0.0;
    }
    return (_time.inSeconds / _length.inSeconds * 100);
  }

  Widget _buildSeekSlider() {
    // We can't access the controls' dimensions until after the initial render
    if (_controlsKey.currentContext == null) {
      return SizedBox();
    }

    final RenderBox box = _controlsKey.currentContext.findRenderObject();
    final position = box.localToGlobal(Offset.zero);
    final mediaQuery = MediaQuery.of(context);

    return Positioned(
      top: position.dy - box.size.height + (24 - mediaQuery.padding.top),
      left: 0,
      right: 0,
      child: SliderTheme(
        data: SliderTheme.of(context).copyWith(
          trackShape: FullWidthTrackShape(),
          thumbShape: RoundSliderThumbShape(enabledThumbRadius: 10),
          overlayShape: RoundSliderOverlayShape(overlayRadius: 16),
        ),
        child: Slider(
          max: _state != 'stopped' ? 100 : 0,
          value: _seekSliderValue,
          onChangeStart: (percent) {
            setState(() {
              _draggingTime = true;
            });
          },
          onChanged: _state != 'stopped'
              ? (percent) {
                  setState(() {
                    _time = Duration(
                      seconds: (_length.inSeconds / 100 * percent).round(),
                    );
                  });
                }
              : null,
          onChangeEnd: (percent) {
            setState(() {
              _draggingTime = false;
            });
          },
        ),
      ),
    );
  }
}

final Uint8List kTransparentImage = new Uint8List.fromList(<int>[
  0x89,
  0x50,
  0x4E,
  0x47,
  0x0D,
  0x0A,
  0x1A,
  0x0A,
  0x00,
  0x00,
  0x00,
  0x0D,
  0x49,
  0x48,
  0x44,
  0x52,
  0x00,
  0x00,
  0x00,
  0x01,
  0x00,
  0x00,
  0x00,
  0x01,
  0x08,
  0x06,
  0x00,
  0x00,
  0x00,
  0x1F,
  0x15,
  0xC4,
  0x89,
  0x00,
  0x00,
  0x00,
  0x0A,
  0x49,
  0x44,
  0x41,
  0x54,
  0x78,
  0x9C,
  0x63,
  0x00,
  0x01,
  0x00,
  0x00,
  0x05,
  0x00,
  0x01,
  0x0D,
  0x0A,
  0x2D,
  0xB4,
  0x00,
  0x00,
  0x00,
  0x00,
  0x49,
  0x45,
  0x4E,
  0x44,
  0xAE,
]);

              
            
!
999px

Console