Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ optionsTranslation: OptionsTranslation(

> Since version 1.1.0, Chewie supports subtitles.

Chewie allows you to enhance the video playback experience with text overlays. You can add a `List<Subtitle>` to your `ChewieController` and fully customize their appearance using the `subtitleBuilder` function.
Chewie allows you to enhance the video playback experience with text overlays. You can add a `List<Subtitle>` to your `ChewieController`, restyle the default subtitle box with `subtitleStyle`, or replace it entirely with the `subtitleBuilder` function.

### Showing Subtitles by Default

Expand Down Expand Up @@ -228,9 +228,58 @@ Subtitle(
),
```

### Markup in Subtitle Text

Cue text extracted from WebVTT or SubRip files often carries inline markup, and Chewie renders it for you:

```dart
Subtitle(
index: 0,
start: Duration.zero,
end: const Duration(seconds: 10),
text: '<i>The law is the law, Mr. Hancock.</i>',
),
```

`<b>`, `<i>`, `<u>` and `<font color="#rrggbb">` are applied on top of your text style. The other WebVTT cue tags — `<c.class>`, `<v Speaker>`, `<lang xx>`, `<ruby>`/`<rt>` and timestamp tags — are dropped while their text is kept, and escapes such as `&amp;` are decoded. A tag that opens on one line and closes on the next works too.

Parsing is lenient, so cue text is never mangled: `5 < 10` and `<3` are shown as written, an unclosed tag simply runs to the end of the cue, and a stray closing tag is ignored. If you would rather show cue text exactly as it arrives, set `subtitleStyle: SubtitleStyle(renderMarkup: false)`.

### Styling Subtitles

`subtitleStyle` changes how the default subtitle box looks without giving up markup rendering:

```dart
ChewieController(
videoPlayerController: _videoPlayerController,
subtitleStyle: const SubtitleStyle(
textStyle: TextStyle(fontSize: 22, color: Colors.amber),
textAlign: TextAlign.center,
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(color: Colors.black54),
),
);
```

Leaving `textStyle.color` unset inherits the colour from the surrounding `DefaultTextStyle`, which is what Chewie does by default.

### Customizing Subtitles

Use the `subtitleBuilder` function to customize how subtitles are rendered, allowing you to modify text styles, add padding, or apply other customizations to your subtitles.
Reach for `subtitleBuilder` when you need to build the whole widget yourself. It receives the cue exactly as you supplied it — markup and all — and Chewie's own rendering, including `subtitleStyle`, is skipped. Run the cue through `parseSubtitleMarkup` to keep markup working:

```dart
subtitleBuilder: (context, subtitle) => Container(
padding: const EdgeInsets.all(10.0),
child: Text.rich(
subtitle is String
? parseSubtitleMarkup(
subtitle,
style: const TextStyle(color: Colors.white),
)
: TextSpan(text: subtitle.toString()),
),
),
```

## 🧪 Example

Expand Down
35 changes: 27 additions & 8 deletions example/lib/app/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ class _ChewieDemoState extends State<ChewieDemo> {
// style: TextStyle(color: Colors.amber, fontSize: 22, fontStyle: FontStyle.italic),
// ),
),
Subtitle(
index: 0,
start: const Duration(seconds: 20),
end: const Duration(seconds: 30),
// Markup like this turns up in real WebVTT and SubRip files, and is
// rendered without any help from a subtitleBuilder.
text:
'Cue text can be <i>italic</i>, <b>bold</b> or '
'<font color="#ffc107">coloured</font>.',
),
];

_chewieController = ChewieController(
Expand All @@ -126,15 +136,24 @@ class _ChewieDemoState extends State<ChewieDemo> {
},
subtitle: Subtitles(subtitles),
showSubtitles: true,
subtitleBuilder: (context, dynamic subtitle) => Container(
padding: const EdgeInsets.all(10.0),
child: subtitle is InlineSpan
? RichText(text: subtitle)
: Text(
subtitle.toString(),
style: const TextStyle(color: Colors.black),
),
subtitleStyle: const SubtitleStyle(
textStyle: TextStyle(fontSize: 20, color: Colors.white),
),
// subtitleBuilder replaces the default box entirely, so subtitleStyle no
// longer applies and cue markup is yours to handle — parseSubtitleMarkup
// is the same parser chewie uses:
//
// subtitleBuilder: (context, dynamic subtitle) => Container(
// padding: const EdgeInsets.all(10.0),
// child: Text.rich(
// subtitle is InlineSpan
// ? subtitle
// : parseSubtitleMarkup(
// subtitle.toString(),
// style: const TextStyle(color: Colors.white),
// ),
// ),
// ),

hideControlsTimer: const Duration(seconds: 1),

Expand Down
1 change: 1 addition & 0 deletions lib/chewie.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export 'src/material/material_controls.dart';
export 'src/material/material_desktop_controls.dart';
export 'src/material/material_progress_bar.dart';
export 'src/models/index.dart';
export 'src/subtitle_markup.dart';
17 changes: 17 additions & 0 deletions lib/src/chewie_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:chewie/src/chewie_progress_colors.dart';
import 'package:chewie/src/models/option_item.dart';
import 'package:chewie/src/models/options_translation.dart';
import 'package:chewie/src/models/subtitle_model.dart';
import 'package:chewie/src/models/subtitle_style.dart';
import 'package:chewie/src/notifiers/player_notifier.dart';
import 'package:chewie/src/player_with_controls.dart';
import 'package:flutter/foundation.dart';
Expand Down Expand Up @@ -316,6 +317,7 @@ class ChewieController extends ChangeNotifier {
this.subtitle,
this.showSubtitles = false,
this.subtitleBuilder,
this.subtitleStyle = const SubtitleStyle(),
this.customControls,
this.errorBuilder,
this.bufferingBuilder,
Expand Down Expand Up @@ -369,6 +371,7 @@ class ChewieController extends ChangeNotifier {
Subtitles? subtitle,
bool? showSubtitles,
Widget Function(BuildContext, dynamic)? subtitleBuilder,
SubtitleStyle? subtitleStyle,
Widget? customControls,
WidgetBuilder? bufferingBuilder,
Widget Function(BuildContext, String)? errorBuilder,
Expand Down Expand Up @@ -431,6 +434,7 @@ class ChewieController extends ChangeNotifier {
showSubtitles: showSubtitles ?? this.showSubtitles,
subtitle: subtitle ?? this.subtitle,
subtitleBuilder: subtitleBuilder ?? this.subtitleBuilder,
subtitleStyle: subtitleStyle ?? this.subtitleStyle,
customControls: customControls ?? this.customControls,
errorBuilder: errorBuilder ?? this.errorBuilder,
bufferingBuilder: bufferingBuilder ?? this.bufferingBuilder,
Expand Down Expand Up @@ -491,11 +495,24 @@ class ChewieController extends ChangeNotifier {
final List<OptionItem> Function(BuildContext context)? additionalOptions;

/// Define here your own Widget on how your n'th subtitle will look like
///
/// Receives the cue exactly as it was supplied, markup and all. Chewie's own
/// rendering — including [SubtitleStyle] and markup parsing — is skipped
/// entirely. To keep markup while building your own widget, run the cue
/// through `parseSubtitleMarkup` yourself.
Widget Function(BuildContext context, dynamic subtitle)? subtitleBuilder;

/// Add a List of Subtitles here in `Subtitles.subtitle`
Subtitles? subtitle;

/// How the default subtitle box looks: text style, alignment, padding and
/// the box behind the text.
///
/// Cue markup such as `<i>` is rendered whatever this is set to, so styling
/// subtitles does not cost you italics. Ignored when [subtitleBuilder] is
/// set.
SubtitleStyle subtitleStyle;

/// Determines whether subtitles should be shown by default when the video starts.
///
/// If set to `true`, subtitles will be displayed automatically when the video
Expand Down
26 changes: 5 additions & 21 deletions lib/src/cupertino/cupertino_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:chewie/src/helpers/utils.dart';
import 'package:chewie/src/models/option_item.dart';
import 'package:chewie/src/models/subtitle_model.dart';
import 'package:chewie/src/notifiers/index.dart';
import 'package:chewie/src/subtitle_overlay.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
Expand Down Expand Up @@ -205,27 +206,10 @@ class _CupertinoControlsState extends State<CupertinoControls>
return const SizedBox();
}

if (chewieController.subtitleBuilder != null) {
return chewieController.subtitleBuilder!(
context,
currentSubtitle.first!.text,
);
}

return Padding(
padding: EdgeInsets.only(left: marginSize, right: marginSize),
child: Container(
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: const Color(0x96000000),
borderRadius: BorderRadius.circular(10.0),
),
child: Text(
currentSubtitle.first!.text.toString(),
style: const TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
),
return SubtitleOverlay(
chewieController: chewieController,
margin: EdgeInsets.only(left: marginSize, right: marginSize),
text: currentSubtitle.first!.text,
);
}

Expand Down
26 changes: 5 additions & 21 deletions lib/src/material/material_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:chewie/src/material/widgets/playback_speed_dialog.dart';
import 'package:chewie/src/models/option_item.dart';
import 'package:chewie/src/models/subtitle_model.dart';
import 'package:chewie/src/notifiers/index.dart';
import 'package:chewie/src/subtitle_overlay.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:video_player/video_player.dart';
Expand Down Expand Up @@ -217,27 +218,10 @@ class _MaterialControlsState extends State<MaterialControls>
return const SizedBox();
}

if (chewieController.subtitleBuilder != null) {
return chewieController.subtitleBuilder!(
context,
currentSubtitle.first!.text,
);
}

return Padding(
padding: EdgeInsets.all(marginSize),
child: Container(
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: const Color(0x96000000),
borderRadius: BorderRadius.circular(10.0),
),
child: Text(
currentSubtitle.first!.text.toString(),
style: const TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
),
return SubtitleOverlay(
chewieController: chewieController,
margin: EdgeInsets.all(marginSize),
text: currentSubtitle.first!.text,
);
}

Expand Down
26 changes: 5 additions & 21 deletions lib/src/material/material_desktop_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:chewie/src/material/widgets/playback_speed_dialog.dart';
import 'package:chewie/src/models/option_item.dart';
import 'package:chewie/src/models/subtitle_model.dart';
import 'package:chewie/src/notifiers/index.dart';
import 'package:chewie/src/subtitle_overlay.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
Expand Down Expand Up @@ -230,27 +231,10 @@ class _MaterialDesktopControlsState extends State<MaterialDesktopControls>
return const SizedBox();
}

if (chewieController.subtitleBuilder != null) {
return chewieController.subtitleBuilder!(
context,
currentSubtitle.first!.text,
);
}

return Padding(
padding: EdgeInsets.all(marginSize),
child: Container(
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: const Color(0x96000000),
borderRadius: BorderRadius.circular(10.0),
),
child: Text(
currentSubtitle.first!.text.toString(),
style: const TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
),
return SubtitleOverlay(
chewieController: chewieController,
margin: EdgeInsets.all(marginSize),
text: currentSubtitle.first!.text,
);
}

Expand Down
1 change: 1 addition & 0 deletions lib/src/models/index.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'option_item.dart';
export 'options_translation.dart';
export 'subtitle_model.dart';
export 'subtitle_style.dart';
Loading