-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[video_player] VTT Support #2878
Changes from 3 commits
9788b63
a66f5d7
6481331
8f8b095
da1c6e4
1a21621
0d3549e
b018ea9
b92f305
1573670
1e53fc5
9034263
d9647a9
2ebf99f
43abfd6
a44edea
d92e820
10e717c
98e21c1
e146366
c05a6f1
50044cc
2aacc0f
928bfb4
ee9e13a
bf3e958
91bba0f
459b7f8
b43b1a8
db547b6
7864399
7afa2c2
a8a5994
0fdc514
1ca27a7
39ba0ae
7391c73
00efa43
8e1dcad
9fdcb05
f0e286b
1350f8e
c72a068
226b9c6
4554771
0b7dde4
7aa17ba
2f686c2
5d09170
78098ca
46aecd8
cacf4dc
5524931
110c115
808b1a0
a041f54
2a16e2e
acf7417
de3c973
495b37c
fc0d049
9db4ec1
c5bbba6
7bb0d53
d47ea7f
4c66c38
db79449
fa0d6dc
a4fcbf7
61a22a5
14468e5
0d68872
c97876c
6c457a5
5ffff31
c8f7c2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| WEBVTT - This file has cues. | ||
|
|
||
| 14 | ||
| 00:00:01.815 --> 00:00:02.114 | ||
| - What? | ||
| - Where are we now? | ||
|
|
||
| 15 | ||
| 00:01:18.171 --> 00:01:20.991 | ||
| - This is big bat country. | ||
|
|
||
| 16 | ||
| 00:01:21.058 --> 00:01:23.868 | ||
| - [ Bats Screeching ] | ||
| - They won't get in your hair. They're after the bugs. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # | ||
| # NOTE: This podspec is NOT to be published. It is only used as a local source! | ||
| # | ||
|
|
||
| Pod::Spec.new do |s| | ||
| s.name = 'Flutter' | ||
| s.version = '1.0.0' | ||
| s.summary = 'High-performance, high-fidelity mobile apps.' | ||
| s.description = <<-DESC | ||
| Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. | ||
| DESC | ||
| s.homepage = 'https://flutter.io' | ||
| s.license = { :type => 'MIT' } | ||
| s.author = { 'Flutter Dev Team' => '[email protected]' } | ||
| s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } | ||
| s.ios.deployment_target = '8.0' | ||
| s.vendored_frameworks = 'Flutter.framework' | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| // Copyright 2020 The Chromium Authors. All rights reserved. | ||
|
||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| import 'dart:convert'; | ||
cyanglaz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| import 'closed_caption_file.dart'; | ||
| import 'package:html/parser.dart'; | ||
ferrazrx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// Represents a [ClosedCaptionFile], parsed from the WebVTT file format. | ||
| /// See: https://en.wikipedia.org/wiki/WebVTT | ||
| class WebVTTCaptionFile extends ClosedCaptionFile { | ||
ferrazrx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /// Parses a string into a [ClosedCaptionFile], assuming [fileContents] is in | ||
| /// the WebVTT file format. | ||
| /// * See: https://en.wikipedia.org/wiki/WebVTT | ||
| WebVTTCaptionFile(this.fileContents) | ||
| : _captions = _parseCaptionsFromWebVTTString(fileContents); | ||
|
|
||
| /// The entire body of the VTT file. | ||
| final String fileContents; | ||
|
||
|
|
||
| @override | ||
| List<Caption> get captions => _captions; | ||
|
|
||
| final List<Caption> _captions; | ||
| } | ||
|
|
||
| List<Caption> _parseCaptionsFromWebVTTString(String file) { | ||
| final List<Caption> captions = <Caption>[]; | ||
| int number = 1; | ||
ferrazrx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| for (List<String> captionLines in _readWebVTTFile(file)) { | ||
| if (captionLines.length < 2) continue; | ||
cyanglaz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Caption has header | ||
| bool hasHeader = captionLines.length > 2; | ||
| if (hasHeader) { | ||
| number = int.parse(captionLines[0]); | ||
| } | ||
|
|
||
| final int captionNumber = number; | ||
| final _StartAndEnd startAndEnd = _StartAndEnd.fromWebVTTString( | ||
| hasHeader ? captionLines[1] : captionLines[0]); | ||
ferrazrx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| final String text = captionLines.sublist(hasHeader ? 2 : 1).join('\n'); | ||
|
|
||
| //TODO: Handle text format | ||
ferrazrx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| final String textWithoutFormat = _parseHtmlString(text); | ||
|
|
||
| final Caption newCaption = Caption( | ||
| number: captionNumber, | ||
| start: startAndEnd.start, | ||
| end: startAndEnd.end, | ||
| text: textWithoutFormat, | ||
| ); | ||
|
|
||
| if (newCaption.start != null && newCaption.end != null) { | ||
| captions.add(newCaption); | ||
| number++; | ||
| } | ||
| } | ||
|
|
||
| return captions; | ||
| } | ||
|
|
||
| class _StartAndEnd { | ||
|
||
| final Duration start; | ||
| final Duration end; | ||
|
|
||
| _StartAndEnd(this.start, this.end); | ||
|
|
||
| // Assumes format from an VTT file. | ||
| // For example: | ||
| // 00:09.000 --> 00:11.000 | ||
| static _StartAndEnd fromWebVTTString(String line) { | ||
| final RegExp format = | ||
| RegExp(_webVTTTimeStamp + _webVTTArrow + _webVTTTimeStamp); | ||
|
|
||
| if (!format.hasMatch(line)) { | ||
| return _StartAndEnd(null, null); | ||
| } | ||
|
|
||
| final List<String> times = line.split(_webVTTArrow); | ||
|
|
||
| final Duration start = _parseWebVTTTimestamp(times[0]); | ||
| final Duration end = _parseWebVTTTimestamp(times[1]); | ||
|
|
||
| return _StartAndEnd(start, end); | ||
| } | ||
| } | ||
|
|
||
| String _parseHtmlString(String htmlString) { | ||
|
||
| var document = parse(htmlString); | ||
ferrazrx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| String parsedString = parse(document.body.text).documentElement.text; | ||
ferrazrx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return parsedString; | ||
| } | ||
|
|
||
| // Parses a time stamp in an VTT file into a Duration. | ||
| // For example: | ||
| // | ||
| // _parseWebVTTimestamp('00:01:08.430') | ||
| // returns | ||
| // Duration(hours: 0, minutes: 1, seconds: 8, milliseconds: 430) | ||
cyanglaz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Duration _parseWebVTTTimestamp(String timestampString) { | ||
| if (!RegExp(_webVTTTimeStamp).hasMatch(timestampString)) { | ||
| return null; | ||
| } | ||
|
|
||
| final List<String> dotSections = timestampString.split('.'); | ||
| final List<String> hoursMinutesSeconds = dotSections[0].split(':'); | ||
|
||
|
|
||
| int hours = 0; | ||
| int minutes = 0; | ||
| int seconds = 0; | ||
| List<String> styles; | ||
|
|
||
| if (hoursMinutesSeconds.length > 2) { | ||
ferrazrx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] | ||
| hours = int.parse(hoursMinutesSeconds[0]); | ||
| minutes = int.parse(hoursMinutesSeconds[1]); | ||
| seconds = int.parse(hoursMinutesSeconds[2]); | ||
| } else if (int.parse(hoursMinutesSeconds[0]) > 59) { | ||
| // Timestamp takes the form of [hours]:[minutes].[milliseconds] | ||
|
||
| // First position is hours as it's over 59. | ||
| hours = int.parse(hoursMinutesSeconds[0]); | ||
| minutes = int.parse(hoursMinutesSeconds[1]); | ||
| } else { | ||
| // Timestamp takes the form of [minutes]:[seconds].[milliseconds] | ||
| minutes = int.parse(hoursMinutesSeconds[0]); | ||
| seconds = int.parse(hoursMinutesSeconds[1]); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once the above is removed, this can all condense to: It would be good to add a safety check that the length is either 2 or 3 and returns null first, as well.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
|
||
| List<String> milisecondsStyles = dotSections[1].split(" "); | ||
| //TODO: Handle styles data on timestamp | ||
| if (milisecondsStyles.length > 1) { | ||
| styles = milisecondsStyles.sublist(1); | ||
| } | ||
| int milliseconds = int.parse(milisecondsStyles[0]); | ||
|
|
||
| return Duration( | ||
| hours: hours, | ||
| minutes: minutes, | ||
| seconds: seconds, | ||
| milliseconds: milliseconds, | ||
| ); | ||
| } | ||
|
|
||
| // Reads on VTT file and splits it into Lists of strings where each list is one | ||
| // caption. | ||
| List<List<String>> _readWebVTTFile(String file) { | ||
| final List<String> lines = LineSplitter.split(file).toList(); | ||
|
|
||
| final List<List<String>> captionStrings = <List<String>>[]; | ||
| List<String> currentCaption = <String>[]; | ||
| int lineIndex = 0; | ||
| for (final String line in lines) { | ||
| final bool isLineBlank = line.trim().isEmpty; | ||
| if (!isLineBlank) { | ||
| currentCaption.add(line); | ||
| } | ||
|
|
||
| if (isLineBlank || lineIndex == lines.length - 1) { | ||
| captionStrings.add(currentCaption); | ||
| currentCaption = <String>[]; | ||
| } | ||
|
|
||
| lineIndex += 1; | ||
| } | ||
|
|
||
| return captionStrings; | ||
| } | ||
|
|
||
| const String _webVTTTimeStamp = r'(\d+):(\d{2})(:\d{2})?\.(\d{3})'; | ||
| const String _webVTTArrow = r' --> '; | ||
Uh oh!
There was an error while loading. Please reload this page.