Skip to content

Commit

Permalink
Support subtitles with 0.2.0 version.
Browse files Browse the repository at this point in the history
  • Loading branch information
MuhmdHsn313 committed May 4, 2020
2 parents e8796d3 + 9a36a0b commit 8884d4d
Show file tree
Hide file tree
Showing 19 changed files with 492 additions and 79 deletions.
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## [0.0.1] - TODO: Add release date.
## [0.1.0] - Spring.

* TODO: Describe initial release.
* Added `IQScreen` class that enable user to use a plyer as a screen.

## [0.2.0] - Storm.

* Added `SubtitleProvider` class that enable user to use a subtitle from files, assets, network, string.
* Added `IQParser` class to display subtitle data.
* Added `SubtitleBloc` class to use with `IQParser`.
58 changes: 51 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,24 @@ Simple video player with subtitle wrote for flutter.
## Features
1. [x] Play video from Assets, Files, Network by `VideoPlayerController` from video_player.
2. [ ] Parse subtitles from Assets, Files, Network `SubtitleProvider` class.
2. [x] Parse subtitles from Assets, Files, Network `SubtitleProvider` class.
3. [ ] Custom theme you can use with `IQTheme` class.
4. [ ] Support Subtitles:
1. [ ] VTT format
4. [x] Support Subtitles:
1. [x] VTT format
2. [ ] SRT format
3. [ ] User define format
5. [x] **IQScreen:** a video player scaffold screen.
6. [ ] **IQPlayer:** a widget enable you to watch video implement with your screen.
7. [ ] **IQParser:** a subtitle package that view subtitles, included the widget and parser
7. [x] **IQParser:** a subtitle package that view subtitles, included the widget and parser


# Installation
## 1. Depend on
Go to `pubspec.yaml` and set the dependencies like:

```yaml
dependencies:
iqplayer: ^0.0.1
iqplayer: <latest_version>
```
Install packages from the command line with Flutter:
Expand All @@ -50,7 +51,8 @@ The Flutter project template adds it, so it may already be there.
Warning: The video player is not functional on iOS simulators. An iOS device must be used during development/testing.

Add the following entry to your Info.plist file, located in <project root>/ios/Runner/Info.plist:
```plist

```
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
Expand All @@ -72,6 +74,7 @@ import "package:iqplayer/iqplayer.dart";
```dart
IQScreen(
videoPlayerController = VideoPlayerController.network(""),
subtitleProvider: SubtitleProvider.fromNetwork(""),
title: "Simple Video",
description: "Simple Description",
);
Expand All @@ -81,7 +84,18 @@ IQScreen(
> In development.
3. **IQParser:**
> In development.

> Note: It is used automatically with `IQScreen` and you can use and display data with `SubtitleProvider`.
```dart
BlocProvider<SubtitleBloc>(
create: (context) =>
SubtitleBloc(
SubtitleProvider.fromNetwork(""),
)..add(FetchSubtitles()),
child: MyParser(),
);
```

# Using

Expand All @@ -95,11 +109,38 @@ Navigator.push(
title: "",
description: "",
videoPlayerController: VideoPlayerController.network(""),
subtitleProvider: SubtitleProvider.fromNetwork(""),
),
),
);
```

2. Using of `IQParser`:

> You have to use `BlocProvider` with `SubtitleProvider` to configure subtitles.
```dart
// In Your widget
BlocProvider<SubtitleBloc>(
create: (context) =>
SubtitleBloc(
SubtitleProvider.fromNetwork(""),
)..add(FetchSubtitles()),
child: MyParser(),
);
// new parser class, you can exclude `MyParser`
class MyParser extends StatelessWidget {
@override
Widget build(BuildContext context) {
return IQParser();
}
}
```
> Note: You can exclude "MyParser" and delete it!
> Note: What is the reason for creating `MyParser`? [see this](https://bloclibrary.dev/#/faqs?id=blocproviderof-fails-to-find-bloc)
# Example
```dart
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -147,6 +188,9 @@ class MyHomePage extends StatelessWidget {
videoPlayerController: VideoPlayerController.network(
'https://d11b76aq44vj33.cloudfront.net/media/720/video/5def7824adbbc.mp4',
),
subtitleProvider: SubtitleProvider.fromNetwork(
'https://duoidi6ujfbv.cloudfront.net/media/0/subtitles/5675420c9d9a3.vtt'
),
),
),
);
Expand Down
3 changes: 3 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class MyHomePage extends StatelessWidget {
videoPlayerController: VideoPlayerController.network(
'https://d11b76aq44vj33.cloudfront.net/media/720/video/5def7824adbbc.mp4',
),
subtitleProvider: SubtitleProvider.fromNetwork(
'https://duoidi6ujfbv.cloudfront.net/media/0/subtitles/5675420c9d9a3.vtt'
),
),
),
);
Expand Down
5 changes: 4 additions & 1 deletion lib/iqplayer.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
library iqplayer;

export 'src/ui/iqscreen.dart';
export 'package:video_player/video_player.dart';
export 'src/utils/subtitle_provider.dart';
export 'src/ui/iqparser.dart';
export 'package:video_player/video_player.dart'
show VideoPlayerController, VideoPlayerValue;
11 changes: 5 additions & 6 deletions lib/src/blocs/player/player_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class PlayerBloc extends Bloc<PlayerEvent, PlayerState> {
) async* {
if (event is FetchVideo) {
yield LoadingState();
if (value.buffered.last.end == value.duration) {
if (value?.buffered?.last?.end == value?.duration) {
controller.seekTo(Duration.zero);
add(PlayVideo());
} else if (value.hasError || !value.initialized) {
Expand All @@ -32,10 +32,10 @@ class PlayerBloc extends Bloc<PlayerEvent, PlayerState> {
}

if (event is PlayVideo) {
if (!value.isPlaying) controller.play();
if (value != null) if (!value.isPlaying) controller.play();
yield PlayingState(
duration: value.duration,
position: value.position,
duration: value?.duration ?? Duration.zero,
position: value?.position ?? Duration.zero,
);
}
if (event is PauseVideo) {
Expand Down Expand Up @@ -79,8 +79,7 @@ class PlayerBloc extends Bloc<PlayerEvent, PlayerState> {

void _listener() {
value = controller.value;
print((value.buffered.last.end + Duration(seconds: 1)) == value.duration);
if (value.buffered.last.end == value.duration) {
if (value.buffered?.last?.end == value.duration) {
add(
FinishVideo(
position: value.position,
Expand Down
2 changes: 1 addition & 1 deletion lib/src/blocs/screen/bloc.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export 'screen_bloc.dart';
export 'screen_event.dart';
export 'screen_state.dart';
export 'screen_state.dart';
3 changes: 3 additions & 0 deletions lib/src/blocs/subtitle/bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export 'subtitle_bloc.dart';
export 'subtitle_event.dart';
export 'subtitle_state.dart';
41 changes: 41 additions & 0 deletions lib/src/blocs/subtitle/subtitle_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:iqplayer/src/models/subtitle.dart';
import 'package:iqplayer/src/utils/subtitle_provider.dart';
import 'package:iqplayer/src/utils/subtitle_controller.dart';
import './bloc.dart';

class SubtitleBloc extends Bloc<SubtitleEvent, SubtitleState> {
final SubtitleProvider _subtitleProvider;
final SubtitleController _subtitleController;

List<Subtitle> subtitles;

SubtitleBloc(this._subtitleProvider)
: subtitles = new List<Subtitle>(),
_subtitleController = new SubtitleController();

@override
SubtitleState get initialState => SubtitleState.initial();

@override
Stream<SubtitleState> mapEventToState(
SubtitleEvent event,
) async* {
if (event is FetchSubtitles) {
subtitles = await _subtitleController.fetchList(_subtitleProvider.data);
}

if (event is UpdateSubtitle) {
for (Subtitle subtitle in subtitles) {
if (event.position >= subtitle.start &&
event.position <= subtitle.end) {
yield SubtitleState(subtitle.data);
break;
} else {
yield SubtitleState.initial();
}
}
}
}
}
22 changes: 22 additions & 0 deletions lib/src/blocs/subtitle/subtitle_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:equatable/equatable.dart';

abstract class SubtitleEvent extends Equatable {
const SubtitleEvent();

@override
List<Object> get props => [];
}

class FetchSubtitles extends SubtitleEvent {}

class UpdateSubtitle extends SubtitleEvent {
final Duration position;

const UpdateSubtitle(this.position);

@override
List<Object> get props => [position];

@override
String toString() => "Position { duration: $position }";
}
22 changes: 22 additions & 0 deletions lib/src/blocs/subtitle/subtitle_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:iqplayer/src/models/subtitle.dart';

class SubtitleState extends Equatable {
final String data;

const SubtitleState(this.data);

factory SubtitleState.initial() => SubtitleState(
''
);

SubtitleState copyWith(Subtitle subtitle) =>
SubtitleState(subtitle.data ?? this.data);

@override
List<Object> get props => [data];

@override
String toString() => "SubtitleState { date: $data }";
}
30 changes: 30 additions & 0 deletions lib/src/models/subtitle.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';

class Subtitle extends Equatable {
final Duration start;
final Duration end;
final String data;

const Subtitle({
@required this.start,
@required this.end,
@required this.data,
}) : assert(start != null),
assert(end != null),
assert(data != null);

bool operator >(Subtitle other) => this.start > other.start;

bool operator <(Subtitle other) => this.start < other.start;

bool operator <=(Subtitle other) => this.start <= other.start;

bool operator >=(Subtitle other) => this.start >= other.start;

int compareTo(Subtitle other) =>
this.start.inMilliseconds.compareTo(other.start.inMilliseconds);

@override
List<Object> get props => [start, end, data];
}
21 changes: 21 additions & 0 deletions lib/src/repositories/subtitle_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'dart:convert';
import 'dart:io';

import 'package:flutter/services.dart';
import 'package:http/http.dart';

class SubtitleRepository {
Future<String> fetchFromNetwork(String url) async {
final response = await get(url);
if (response.statusCode == 200) return utf8.decode(response.bodyBytes);
throw 'ERROR_FETCH_SUBTITLE(${response.statusCode})';
}

Future<String> fetchFromFile(File file) async {
return await file.readAsString();
}

Future<String> fetchFromAssets(String path) async {
return await rootBundle.loadString(path);
}
}
40 changes: 40 additions & 0 deletions lib/src/ui/iqparser.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:iqplayer/src/blocs/player/bloc.dart';
import 'package:iqplayer/src/blocs/subtitle/bloc.dart';

class IQParser extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<PlayerBloc, PlayerState>(
bloc: BlocProvider.of<PlayerBloc>(context),
listener: (context, state) {
if (state is PlayingState)
BlocProvider.of<SubtitleBloc>(context).add(
UpdateSubtitle(state.position),
);
},
child: BlocBuilder<SubtitleBloc, SubtitleState>(
bloc: BlocProvider.of<SubtitleBloc>(context),
builder: (context, state) => Container(
width: MediaQuery.of(context).size.width,
child: Text(
state.data,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
color: Colors.white,
shadows: [
Shadow(
color: Colors.black,
offset: Offset(1, 1),
blurRadius: 2.5,
)
],
),
),
),
),
);
}
}
Loading

0 comments on commit 8884d4d

Please sign in to comment.