diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b1a9f80..38a1c9bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,9 @@ name: CI on: push: + branches: [master] pull_request: + branches: [master] jobs: check-format: @@ -14,7 +16,7 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - name: Check format - run: flutter format --set-exit-if-changed . + run: dart format --set-exit-if-changed . lint: name: Check lints diff --git a/CHANGELOG.md b/CHANGELOG.md index 26acffa1..26d68b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +## 0.2.2 +- fixed broken vimeo api requests +- upgraded minimum sdk version from ">=2.17.0 <4.0.0" to ">=3.0.0 <4.0.0" +- upgraded all the dependencies to latest version +- fix typos in README.md + +## 0.2.1 +- upgraded all dependencies +- upgraded `http` lib to `^1.1.0` +- fix fullscreen issue in IOS + +## 0.2.0 + - upgraded dependencies + - migrate from `wakelock` to `wakelock_plus` PR [#129](https://github.com/newtaDev/pod_player/pull/129) + - fixed all lint rules + - migrated `VideoPlayerController.network` to `VideoPlayerController.networkUrl` + - Breaking: + - In `PlayVideoFrom.file` [file] param datatype changed from [dynamic] to [File] +## 0.1.5 + - merged PR #103 + - support unlisted vimeo videos + - upgraded dependencies + - Updated Readme file +## 0.1.4 + - added pod player logo to pub.dev +## 0.1.3 + - fix: unable to find directory entry in pubspec.yaml #114 + - merged PR #109 +## 0.1.2 + - fixed #82 +## 0.1.1 + - Feature + - support vimeo private video [ref](https://github.com/newtaDev/pod_player#how-to-play-video-from-vimeo-private-videos) + - double tap ripple effect added + - upgraded dependencies + - merged PR #66 #77 #78 ## 0.1.0 - Breaking change: diff --git a/README.md b/README.md index b30edfe0..e5515225 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,14 @@

- pub likes - pub version - popularity - pub points + pub likes + pub version + score + pub points

+

newta

+ Video player for flutter web & mobile devices, pod player supports playing video from `Youtube` and `Vimeo` pod player is a simple and easy-to-use video player. Its video controls are similar to Youtube player (with customizable controls) and also can play videos from `Youtube` and `Vimeo` (By providing url/video_id). @@ -26,8 +28,8 @@ This plugin built upon flutter's official [`video_player`](https://pub.dartlang. ## Features - Play `youtube` videos (using video URL or ID) -- Play `vimeo` videos (using video ID) -- Play `vimeo` private videos (using video ID, access token) +- Play `vimeo` videos (using video ID [with ou without hash]) +- Play `vimeo` private videos (using video ID [with ou without hash], access token) - Video overlay similar to youtube - `Double tap` to seek video. - On video tap show/hide video overlay. @@ -37,24 +39,24 @@ This plugin built upon flutter's official [`video_player`](https://pub.dartlang. - Custom progress bar - Custom labels - `Change video quality` (for vimeo and youtube) -- Enable/disable fullscreen player +- Enable/disable full-screen player - support for live youtube video - [TODO] support for video playlist ## Features on web -- Double tap on Video player to enable/disable fullscreen -- `Mute/unmute` volume +- Double tap on Video player to enable/disable full-screen +- `Mute/unMute` volume - Video player integration with keyboard - `SPACE` play/pause video - `M` mute/unMute video - - `F` enable/disable fullscreen - - `ESC` enable/disable fullscreen + - `F` enable/disable full-screen + - `ESC` enable/disable full-screen - `->` seek video forward - `<-` seek video backward -- Double tap on video (enable/diables fullscreen) +- Double tap on video (enable/disables full-screen) ## Demo @@ -68,8 +70,14 @@ This plugin built upon flutter's official [`video_player`](https://pub.dartlang. pod_player +- Video player on web + --- +

+ pod_player +

+ - Vimeo player and custom video player --- @@ -90,7 +98,7 @@ This plugin built upon flutter's official [`video_player`](https://pub.dartlang. --- -- On mobile full screen +- On mobile full-screen --- @@ -116,12 +124,6 @@ This plugin built upon flutter's official [`video_player`](https://pub.dartlang. --- ---- - -

- pod_player -

- ## Usage - [Installation](#installation) @@ -380,6 +382,49 @@ class _PlayVideoFromVimeoState extends State { } ``` + +## How to play video from vimeo with hash + +--- + +```dart +import 'package:pod_player/pod_player.dart'; +import 'package:flutter/material.dart'; + +class PlayVideoFromVimeo extends StatefulWidget { + const PlayVideoFromVimeo({Key? key}) : super(key: key); + + @override + State createState() => _PlayVideoFromVimeoState(); +} + +class _PlayVideoFromVimeoState extends State { + late final PodPlayerController controller; + + @override + void initState() { + controller = PodPlayerController( + playVideoFrom: PlayVideoFrom.vimeo('518228118', hash: '7cc595e1f8'), + )..initialise(); + super.initState(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: PodVideoPlayer(controller: controller), + ); + } +} + +``` + ## How to play video from vimeo private videos --- @@ -396,14 +441,14 @@ class PlayVideoFromVimeoPrivateVideo extends StatefulWidget { _PlayVideoFromVimeoPrivateVideoState(); } -class _PlayVideoFromVimeoPrivateVideoState +class _PlayVideoFromVimeoPrivateVideoState extends State { late final PodPlayerController controller; @override void initState() { - String videoId = 'your private video id'; - String token = 'your access token'; + String videoId = 'your private video id'; + String token = 'your access token'; final Map headers = {}; headers['Authorization'] = 'Bearer ${token}'; diff --git a/analysis_options.yaml b/analysis_options.yaml index a547bf2a..18aea978 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,19 +1,17 @@ include: package:very_good_analysis/analysis_options.yaml analyzer: - strong-mode: - implicit-casts: true - implicit-dynamic: true errors: missing_required_param: error prefer_const_declarations: warning prefer_const_constructors: warning import_of_legacy_library_into_null_safe: warning public_member_api_docs: ignore - + language: + strict-casts: true + strict-raw-types: true linter: rules: omit_local_variable_types: false - missing_required_param: true prefer_const_declarations: true prefer_const_constructors: true public_member_api_docs: false @@ -22,7 +20,6 @@ linter: lines_longer_than_80_chars: false prefer_relative_imports: true always_use_package_imports: false - import_of_legacy_library_into_null_safe: true avoid_print: true prefer_single_quotes: true avoid_redundant_argument_values: true @@ -34,4 +31,4 @@ linter: avoid_positional_boolean_parameters: false use_build_context_synchronously: false use_setters_to_change_properties: false - avoid_bool_literals_in_conditional_expressions: false \ No newline at end of file + avoid_bool_literals_in_conditional_expressions: false diff --git a/assets/forward_left.json b/assets/forward_left.json deleted file mode 100644 index 13c8a66e..00000000 --- a/assets/forward_left.json +++ /dev/null @@ -1,347 +0,0 @@ -{ - "v": "5.5.7", - "fr": 30, - "ip": 0, - "op": 31, - "w": 360, - "h": 360, - "nm": "Comp 20", - "ddd": 0, - "assets": [], - "layers": [ - { - "ddd": 0, - "ind": 1, - "ty": 4, - "nm": "Layer 3", - "sr": 1, - "ks": { - "o": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 10, - "s": [0] - }, - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 20, - "s": [100] - }, - { "t": 30, "s": [0] } - ], - "ix": 11 - }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [161.086, 180, 0], "ix": 2 }, - "a": { "a": 0, "k": [-18.914, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, -0.923], - [0, 0], - [0.777, 0.499], - [0, 0], - [-0.714, 0.459], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0.923], - [0, 0], - [-0.714, -0.459], - [0, 0], - [0.777, -0.499] - ], - "v": [ - [-11.243, -8.363], - [-11.243, 8.363], - [-13.041, 9.344], - [-26.05, 0.981], - [-26.05, -0.981], - [-13.041, -9.344] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 300, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 2, - "ty": 4, - "nm": "Layer 1", - "sr": 1, - "ks": { - "o": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 5, - "s": [0] - }, - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 15, - "s": [100] - }, - { "t": 25, "s": [0] } - ], - "ix": 11 - }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [180.086, 180, 0], "ix": 2 }, - "a": { "a": 0, "k": [0.086, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, -0.923], - [0, 0], - [0.777, 0.499], - [0, 0], - [-0.714, 0.459], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0.923], - [0, 0], - [-0.714, -0.459], - [0, 0], - [0.777, -0.499] - ], - "v": [ - [7.757, -8.363], - [7.757, 8.363], - [5.959, 9.344], - [-7.05, 0.981], - [-7.05, -0.981], - [5.959, -9.344] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 300, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 3, - "ty": 4, - "nm": "Layer 2", - "sr": 1, - "ks": { - "o": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 0, - "s": [0] - }, - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 10, - "s": [100] - }, - { "t": 20, "s": [0] } - ], - "ix": 11 - }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [198.914, 180, 0], "ix": 2 }, - "a": { "a": 0, "k": [18.914, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, -0.923], - [0, 0], - [0.777, 0.499], - [0, 0], - [-0.714, 0.459], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0.923], - [0, 0], - [-0.714, -0.459], - [0, 0], - [0.777, -0.499] - ], - "v": [ - [26.586, -8.363], - [26.586, 8.363], - [24.788, 9.344], - [11.779, 0.981], - [11.779, -0.981], - [24.788, -9.344] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 300, - "st": 0, - "bm": 0 - } - ], - "markers": [] -} diff --git a/assets/forward_right.json b/assets/forward_right.json deleted file mode 100644 index 90928280..00000000 --- a/assets/forward_right.json +++ /dev/null @@ -1,347 +0,0 @@ -{ - "v": "5.5.7", - "fr": 30, - "ip": 0, - "op": 31, - "w": 360, - "h": 360, - "nm": "forward right", - "ddd": 0, - "assets": [], - "layers": [ - { - "ddd": 0, - "ind": 1, - "ty": 4, - "nm": "Layer 2", - "sr": 1, - "ks": { - "o": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 0, - "s": [0] - }, - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 10, - "s": [100] - }, - { "t": 20, "s": [0] } - ], - "ix": 11 - }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [161.086, 180, 0], "ix": 2 }, - "a": { "a": 0, "k": [-18.914, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0.923], - [0, 0], - [-0.777, -0.499], - [0, 0], - [0.714, -0.459], - [0, 0] - ], - "o": [ - [0, 0], - [0, -0.923], - [0, 0], - [0.714, 0.459], - [0, 0], - [-0.777, 0.499] - ], - "v": [ - [-26.586, 8.363], - [-26.586, -8.363], - [-24.788, -9.344], - [-11.779, -0.981], - [-11.779, 0.981], - [-24.788, 9.344] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 300, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 2, - "ty": 4, - "nm": "Layer 1", - "sr": 1, - "ks": { - "o": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 5, - "s": [0] - }, - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 15, - "s": [100] - }, - { "t": 25, "s": [0] } - ], - "ix": 11 - }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [179.914, 180, 0], "ix": 2 }, - "a": { "a": 0, "k": [-0.086, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0.923], - [0, 0], - [-0.777, -0.499], - [0, 0], - [0.714, -0.459], - [0, 0] - ], - "o": [ - [0, 0], - [0, -0.923], - [0, 0], - [0.714, 0.459], - [0, 0], - [-0.777, 0.499] - ], - "v": [ - [-7.757, 8.363], - [-7.757, -8.363], - [-5.959, -9.344], - [7.05, -0.981], - [7.05, 0.981], - [-5.959, 9.344] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 300, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 3, - "ty": 4, - "nm": "Layer 3", - "sr": 1, - "ks": { - "o": { - "a": 1, - "k": [ - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 10, - "s": [0] - }, - { - "i": { "x": [0.667], "y": [1] }, - "o": { "x": [0.333], "y": [0] }, - "t": 20, - "s": [100] - }, - { "t": 30, "s": [0] } - ], - "ix": 11 - }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [198.914, 180, 0], "ix": 2 }, - "a": { "a": 0, "k": [18.914, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0.923], - [0, 0], - [-0.777, -0.499], - [0, 0], - [0.714, -0.459], - [0, 0] - ], - "o": [ - [0, 0], - [0, -0.923], - [0, 0], - [0.714, 0.459], - [0, 0], - [-0.777, 0.499] - ], - "v": [ - [11.243, 8.363], - [11.243, -8.363], - [13.041, -9.344], - [26.05, -0.981], - [26.05, 0.981], - [13.041, 9.344] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { "a": 0, "k": [1, 1, 1, 1], "ix": 4 }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 300, - "st": 0, - "bm": 0 - } - ], - "markers": [] -} diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 0833ecfc..7f0f1a52 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -47,7 +47,7 @@ android { applicationId "com.example.example" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion flutter.minSdkVersion + minSdkVersion 19 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/example/android/build.gradle b/example/android/build.gradle index 83ae2200..3cdaac95 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 8d4492f9..7c569640 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 12.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index 1e8c3c90..279576f3 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 5292f26f..9e54c99f 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,28 +1,35 @@ PODS: - Flutter (1.0.0) + - package_info_plus (0.4.5): + - Flutter - video_player_avfoundation (0.0.1): - Flutter - - wakelock (0.0.1): + - FlutterMacOS + - wakelock_plus (0.0.1): - Flutter DEPENDENCIES: - Flutter (from `Flutter`) - - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`) - - wakelock (from `.symlinks/plugins/wakelock/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) + - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) EXTERNAL SOURCES: Flutter: :path: Flutter + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" video_player_avfoundation: - :path: ".symlinks/plugins/video_player_avfoundation/ios" - wakelock: - :path: ".symlinks/plugins/wakelock/ios" + :path: ".symlinks/plugins/video_player_avfoundation/darwin" + wakelock_plus: + :path: ".symlinks/plugins/wakelock_plus/ios" SPEC CHECKSUMS: - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a - video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff - wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579 + wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c +PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 -COCOAPODS: 1.11.2 +COCOAPODS: 1.15.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index c94fba36..325aea03 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -68,7 +68,6 @@ 6E25F060E308BC4D125ED336 /* Pods-Runner.release.xcconfig */, 0B108E1AF19EC89726150E55 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -156,7 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -200,10 +199,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -214,6 +215,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -340,7 +342,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -418,7 +420,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -467,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -492,7 +494,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.podplayer.podexample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a3..5e31d3d3 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -29,6 +31,8 @@ NSAllowsArbitraryLoads + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -48,7 +52,5 @@ UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone - diff --git a/example/lib/screens/from_vimeo_id.dart b/example/lib/screens/from_vimeo_id.dart index d803eb95..93a67efb 100644 --- a/example/lib/screens/from_vimeo_id.dart +++ b/example/lib/screens/from_vimeo_id.dart @@ -11,6 +11,7 @@ class PlayVideoFromVimeoId extends StatefulWidget { class _PlayVideoFromVimeoIdState extends State { late final PodPlayerController controller; final videoTextFieldCtr = TextEditingController(); + final hashTextFieldCtr = TextEditingController(); @override void initState() { @@ -60,6 +61,18 @@ class _PlayVideoFromVimeoIdState extends State { ), ), ), + Expanded( + flex: 2, + child: TextField( + controller: hashTextFieldCtr, + decoration: const InputDecoration( + labelText: 'Enter vimeo hash', + floatingLabelBehavior: FloatingLabelBehavior.always, + hintText: 'ex: ddefbc', + border: OutlineInputBorder(), + ), + ), + ), const SizedBox(width: 10), FocusScope( canRequestFocus: false, @@ -72,8 +85,12 @@ class _PlayVideoFromVimeoIdState extends State { try { snackBar('Loading....'); FocusScope.of(context).unfocus(); + final vimeoHash = hashTextFieldCtr.text; await controller.changeVideo( - playVideoFrom: PlayVideoFrom.vimeo(videoTextFieldCtr.text), + playVideoFrom: PlayVideoFrom.vimeo( + videoTextFieldCtr.text, + hash: vimeoHash.isNotEmpty ? vimeoHash : null, + ), ); if (!mounted) return; ScaffoldMessenger.of(context).hideCurrentSnackBar(); diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 1c2c9b07..8e40e9f0 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,12 @@ import FlutterMacOS import Foundation -import wakelock_macos +import package_info_plus +import video_player_avfoundation +import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) } diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock new file mode 100644 index 00000000..5bfc2eea --- /dev/null +++ b/example/macos/Podfile.lock @@ -0,0 +1,52 @@ +PODS: + - FlutterMacOS (1.0.0) + - media_kit_libs_macos_video (1.0.4): + - FlutterMacOS + - media_kit_native_event_loop (1.0.0): + - FlutterMacOS + - media_kit_video (0.0.1): + - FlutterMacOS + - package_info_plus (0.0.1): + - FlutterMacOS + - screen_brightness_macos (0.1.0): + - FlutterMacOS + - wakelock_plus (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + - media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`) + - media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`) + - media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) + - screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`) + - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + media_kit_libs_macos_video: + :path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos + media_kit_native_event_loop: + :path: Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos + media_kit_video: + :path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos + screen_brightness_macos: + :path: Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos + wakelock_plus: + :path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos + +SPEC CHECKSUMS: + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82 + media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5 + media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5 + package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce + screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda + wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 + +PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 + +COCOAPODS: 1.11.2 diff --git a/lib/pod_player.dart b/lib/pod_player.dart index 566e146a..e7a0d631 100644 --- a/lib/pod_player.dart +++ b/lib/pod_player.dart @@ -1,3 +1,4 @@ +/// Pod Player library library pod_player; export 'package:video_player/video_player.dart'; diff --git a/lib/src/controllers/pod_base_controller.dart b/lib/src/controllers/pod_base_controller.dart index a0ef656f..4edc604c 100644 --- a/lib/src/controllers/pod_base_controller.dart +++ b/lib/src/controllers/pod_base_controller.dart @@ -44,7 +44,7 @@ class _PodBaseController extends GetxController { await _videoCtr!.initialize(); } if (_videoCtr!.value.isInitialized) { - _listneToVideoState(); + // _listneToVideoState(); _listneToVideoPosition(); _listneToVolume(); if (kIsWeb && autoPlay && isMute && !_isWebAutoPlayDone) _webAutoPlay(); @@ -69,20 +69,20 @@ class _PodBaseController extends GetxController { } } - void _listneToVideoState() { - podVideoStateChanger( - _videoCtr!.value.isBuffering || !_videoCtr!.value.isInitialized - ? PodVideoState.loading - : _videoCtr!.value.isPlaying - ? PodVideoState.playing - : PodVideoState.paused, - ); - } + // void _listneToVideoState() { + // podVideoStateChanger( + // _videoCtr!.value.isBuffering || !_videoCtr!.value.isInitialized + // ? PodVideoState.loading + // : _videoCtr!.value.isPlaying + // ? PodVideoState.playing + // : PodVideoState.paused, + // ); + // } ///updates state with id `_podVideoState` - void podVideoStateChanger(PodVideoState? _val, {bool updateUi = true}) { - if (_podVideoState != (_val ?? _podVideoState)) { - _podVideoState = _val ?? _podVideoState; + void podVideoStateChanger(PodVideoState? val, {bool updateUi = true}) { + if (_podVideoState != (val ?? _podVideoState)) { + _podVideoState = val ?? _podVideoState; if (updateUi) { update(['podVideoState']); update(['update-all']); diff --git a/lib/src/controllers/pod_gestures_controller.dart b/lib/src/controllers/pod_gestures_controller.dart index 7dfa1269..5438034a 100644 --- a/lib/src/controllers/pod_gestures_controller.dart +++ b/lib/src/controllers/pod_gestures_controller.dart @@ -24,10 +24,14 @@ class _PodGesturesController extends _PodVideoQualityController { leftDoubleTapduration += seconds ?? doubleTapForwardSeconds, ); seekBackward(Duration(seconds: seconds ?? doubleTapForwardSeconds)); - leftDoubleTapTimer = Timer(const Duration(milliseconds: 1500), () { + update(['double-tap-left']); + leftDoubleTapTimer = Timer(const Duration(milliseconds: 500), () { isLeftDbTapIconVisible = false; updateLeftTapDuration(0); leftDoubleTapTimer?.cancel(); + if (isvideoPlaying) { + playVideo(true); + } isShowOverlay(false); }); } @@ -43,10 +47,14 @@ class _PodGesturesController extends _PodVideoQualityController { rightDubleTapduration += seconds ?? doubleTapForwardSeconds, ); seekForward(Duration(seconds: seconds ?? doubleTapForwardSeconds)); - rightDoubleTapTimer = Timer(const Duration(milliseconds: 1500), () { + update(['double-tap-right']); + rightDoubleTapTimer = Timer(const Duration(milliseconds: 500), () { isRightDbTapIconVisible = false; updateRightTapDuration(0); rightDoubleTapTimer?.cancel(); + if (isvideoPlaying) { + playVideo(true); + } isShowOverlay(false); }); } diff --git a/lib/src/controllers/pod_getx_video_controller.dart b/lib/src/controllers/pod_getx_video_controller.dart index 1197369b..7a46d30d 100644 --- a/lib/src/controllers/pod_getx_video_controller.dart +++ b/lib/src/controllers/pod_getx_video_controller.dart @@ -5,8 +5,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; -import 'package:universal_html/html.dart' as _html; -import 'package:wakelock/wakelock.dart'; +import 'package:universal_html/html.dart' as uni_html; +import 'package:wakelock_plus/wakelock_plus.dart'; import '../../pod_player.dart'; import '../utils/logger.dart'; @@ -69,8 +69,8 @@ class PodGetXVideoController extends _PodGesturesController { update(['update-all']); // ignore: unawaited_futures - Future.delayed(const Duration(milliseconds: 600)) - .then((value) => _isWebAutoPlayDone = true); + Future.delayed(const Duration(milliseconds: 600)) + .then((_) => _isWebAutoPlayDone = true); } catch (e) { podVideoStateChanger(PodVideoState.error); update(['errorState']); @@ -85,8 +85,8 @@ class PodGetXVideoController extends _PodGesturesController { case PodVideoPlayerType.network: /// - _videoCtr = VideoPlayerController.network( - playVideoFrom.dataSource!, + _videoCtr = VideoPlayerController.networkUrl( + Uri.parse(playVideoFrom.dataSource!), closedCaptionFile: playVideoFrom.closedCaptionFile, formatHint: playVideoFrom.formatHint, videoPlayerOptions: playVideoFrom.videoPlayerOptions, @@ -95,58 +95,61 @@ class PodGetXVideoController extends _PodGesturesController { playingVideoUrl = playVideoFrom.dataSource; break; case PodVideoPlayerType.networkQualityUrls: - final _url = await getUrlFromVideoQualityUrls( + final url = await getUrlFromVideoQualityUrls( qualityList: podPlayerConfig.videoQualityPriority, videoUrls: playVideoFrom.videoQualityUrls!, ); /// - _videoCtr = VideoPlayerController.network( - _url, + _videoCtr = VideoPlayerController.networkUrl( + Uri.parse(url), closedCaptionFile: playVideoFrom.closedCaptionFile, formatHint: playVideoFrom.formatHint, videoPlayerOptions: playVideoFrom.videoPlayerOptions, httpHeaders: playVideoFrom.httpHeaders, ); - playingVideoUrl = _url; + playingVideoUrl = url; break; case PodVideoPlayerType.youtube: - final _urls = await getVideoQualityUrlsFromYoutube( + final urls = await getVideoQualityUrlsFromYoutube( playVideoFrom.dataSource!, playVideoFrom.live, ); - final _url = await getUrlFromVideoQualityUrls( + final url = await getUrlFromVideoQualityUrls( qualityList: podPlayerConfig.videoQualityPriority, - videoUrls: _urls, + videoUrls: urls, ); /// - _videoCtr = VideoPlayerController.network( - _url, + _videoCtr = VideoPlayerController.networkUrl( + Uri.parse(url), closedCaptionFile: playVideoFrom.closedCaptionFile, formatHint: playVideoFrom.formatHint, videoPlayerOptions: playVideoFrom.videoPlayerOptions, httpHeaders: playVideoFrom.httpHeaders, ); - playingVideoUrl = _url; + playingVideoUrl = url; break; case PodVideoPlayerType.vimeo: - await getQualityUrlsFromVimeoId(playVideoFrom.dataSource!); - final _url = await getUrlFromVideoQualityUrls( + await getQualityUrlsFromVimeoId( + playVideoFrom.dataSource!, + hash: playVideoFrom.hash, + ); + final url = await getUrlFromVideoQualityUrls( qualityList: podPlayerConfig.videoQualityPriority, videoUrls: vimeoOrVideoUrls, ); - _videoCtr = VideoPlayerController.network( - _url, + _videoCtr = VideoPlayerController.networkUrl( + Uri.parse(url), closedCaptionFile: playVideoFrom.closedCaptionFile, formatHint: playVideoFrom.formatHint, videoPlayerOptions: playVideoFrom.videoPlayerOptions, httpHeaders: playVideoFrom.httpHeaders, ); - playingVideoUrl = _url; + playingVideoUrl = url; break; case PodVideoPlayerType.asset: @@ -179,19 +182,19 @@ class PodGetXVideoController extends _PodGesturesController { playVideoFrom.dataSource!, playVideoFrom.httpHeaders, ); - final _url = await getUrlFromVideoQualityUrls( + final url = await getUrlFromVideoQualityUrls( qualityList: podPlayerConfig.videoQualityPriority, videoUrls: vimeoOrVideoUrls, ); - _videoCtr = VideoPlayerController.network( - _url, + _videoCtr = VideoPlayerController.networkUrl( + Uri.parse(url), closedCaptionFile: playVideoFrom.closedCaptionFile, formatHint: playVideoFrom.formatHint, videoPlayerOptions: playVideoFrom.videoPlayerOptions, httpHeaders: playVideoFrom.httpHeaders, ); - playingVideoUrl = _url; + playingVideoUrl = url; break; } @@ -226,7 +229,7 @@ class PodGetXVideoController extends _PodGesturesController { } if (event.isKeyPressed(LogicalKeyboardKey.escape)) { if (isFullScreen) { - _html.document.exitFullscreen(); + uni_html.document.exitFullscreen(); if (!isWebPopupOverlayOpen) { disableFullScreen(appContext, tag); } @@ -239,12 +242,12 @@ class PodGetXVideoController extends _PodGesturesController { void toggleFullScreenOnWeb(BuildContext context, String tag) { if (isFullScreen) { - _html.document.exitFullscreen(); + uni_html.document.exitFullscreen(); if (!isWebPopupOverlayOpen) { disableFullScreen(context, tag); } } else { - _html.document.documentElement?.requestFullscreen(); + uni_html.document.documentElement?.requestFullscreen(); enableFullScreen(tag); } } @@ -254,18 +257,18 @@ class PodGetXVideoController extends _PodGesturesController { podLog(_podVideoState.toString()); switch (_podVideoState) { case PodVideoState.playing: - if (podPlayerConfig.wakelockEnabled) Wakelock.enable(); + if (podPlayerConfig.wakelockEnabled) WakelockPlus.enable(); playVideo(true); break; case PodVideoState.paused: - if (podPlayerConfig.wakelockEnabled) Wakelock.disable(); + if (podPlayerConfig.wakelockEnabled) WakelockPlus.disable(); playVideo(false); break; case PodVideoState.loading: isShowOverlay(true); break; case PodVideoState.error: - if (podPlayerConfig.wakelockEnabled) Wakelock.disable(); + if (podPlayerConfig.wakelockEnabled) WakelockPlus.disable(); playVideo(false); break; } diff --git a/lib/src/controllers/pod_player_controller.dart b/lib/src/controllers/pod_player_controller.dart index 9734e9cc..229ce884 100644 --- a/lib/src/controllers/pod_player_controller.dart +++ b/lib/src/controllers/pod_player_controller.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; -import 'package:universal_html/html.dart' as _html; -import 'package:wakelock/wakelock.dart'; +import 'package:universal_html/html.dart' as uni_html; +import 'package:wakelock_plus/wakelock_plus.dart'; import '../../pod_player.dart'; import '../utils/logger.dart'; @@ -78,7 +78,7 @@ class PodPlayerController { throw Exception(_initializationError.toString()); } - await Future.delayed(const Duration(milliseconds: 500)); + await Future.delayed(const Duration(milliseconds: 500)); await _checkAndWaitTillInitialized(); } @@ -166,7 +166,7 @@ class PodPlayerController { _ctr.videoCtr?.removeListener(_ctr.videoListner); _ctr.videoCtr?.dispose(); _ctr.removeListenerId('podVideoState', _ctr.podStateListner); - if (podPlayerConfig.wakelockEnabled) Wakelock.disable(); + if (podPlayerConfig.wakelockEnabled) WakelockPlus.disable(); Get.delete( force: true, tag: getTag, @@ -196,17 +196,17 @@ class PodPlayerController { } ///Moves video forward from current duration to `_duration` - Future videoSeekForward(Duration _duration) async { + Future videoSeekForward(Duration duration) async { await _checkAndWaitTillInitialized(); if (!_isCtrInitialised) return; - return _ctr.seekForward(_duration); + return _ctr.seekForward(duration); } ///Moves video backward from current duration to `_duration` - Future videoSeekBackward(Duration _duration) async { + Future videoSeekBackward(Duration duration) async { await _checkAndWaitTillInitialized(); if (!_isCtrInitialised) return; - return _ctr.seekBackward(_duration); + return _ctr.seekBackward(duration); } ///on right double tap @@ -228,7 +228,7 @@ class PodPlayerController { /// If onToggleFullScreen is set, you must handle the device /// orientation by yourself. void enableFullScreen() { - _html.document.documentElement?.requestFullscreen(); + uni_html.document.documentElement?.requestFullscreen(); _ctr.enableFullScreen(getTag); } @@ -237,7 +237,7 @@ class PodPlayerController { /// If onToggleFullScreen is set, you must handle the device /// orientation by yourself. void disableFullScreen(BuildContext context) { - _html.document.exitFullscreen(); + uni_html.document.exitFullscreen(); if (!_ctr.isWebPopupOverlayOpen) { _ctr.disableFullScreen(context, getTag); @@ -256,8 +256,11 @@ class PodPlayerController { return VideoApis.getYoutubeVideoQualityUrls(youtubeIdOrUrl, live); } - static Future?> getVimeoUrls(String videoId) { - return VideoApis.getVimeoVideoQualityUrls(videoId); + static Future?> getVimeoUrls( + String videoId, { + String? hash, + }) { + return VideoApis.getVimeoVideoQualityUrls(videoId, hash); } /// Hide overlay of video diff --git a/lib/src/controllers/pod_video_controller.dart b/lib/src/controllers/pod_video_controller.dart index e9586162..0a0d4ea6 100644 --- a/lib/src/controllers/pod_video_controller.dart +++ b/lib/src/controllers/pod_video_controller.dart @@ -134,21 +134,21 @@ class _PodVideoController extends _PodUiController { } } - void setVideoPlayBack(String _speed) { + void setVideoPlayBack(String speed) { late double pickedSpeed; - if (_speed == 'Normal') { + if (speed == 'Normal') { pickedSpeed = 1.0; _currentPaybackSpeed = 'Normal'; } else { - pickedSpeed = double.parse(_speed.split('x').first); - _currentPaybackSpeed = _speed; + pickedSpeed = double.parse(speed.split('x').first); + _currentPaybackSpeed = speed; } _videoCtr?.setPlaybackSpeed(pickedSpeed); } - Future setLooping(bool _isLooped) async { - isLooping = _isLooped; + Future setLooping(bool isLooped) async { + isLooping = isLooped; await _videoCtr?.setLooping(isLooping); } @@ -200,11 +200,13 @@ class _PodVideoController extends _PodUiController { DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]), - SystemChrome.setPreferredOrientations(DeviceOrientation.values), - SystemChrome.setEnabledSystemUIMode( - SystemUiMode.manual, - overlays: SystemUiOverlay.values, - ), + if (!(defaultTargetPlatform == TargetPlatform.iOS)) ...[ + SystemChrome.setPreferredOrientations(DeviceOrientation.values), + SystemChrome.setEnabledSystemUIMode( + SystemUiMode.manual, + overlays: SystemUiOverlay.values, + ), + ] ]); } @@ -226,7 +228,7 @@ class _PodVideoController extends _PodUiController { Navigator.push( mainContext, - PageRouteBuilder( + PageRouteBuilder( fullscreenDialog: true, pageBuilder: (BuildContext context, _, __) => FullScreenView( tag: tag, @@ -243,14 +245,14 @@ class _PodVideoController extends _PodUiController { } /// Calculates video `position` or `duration` - String calculateVideoDuration(Duration _duration) { - final _totalHour = _duration.inHours == 0 ? '' : '${_duration.inHours}:'; - final _totalMinute = _duration.toString().split(':')[1]; - final _totalSeconds = (_duration - Duration(minutes: _duration.inMinutes)) + String calculateVideoDuration(Duration duration) { + final totalHour = duration.inHours == 0 ? '' : '${duration.inHours}:'; + final totalMinute = duration.toString().split(':')[1]; + final totalSeconds = (duration - Duration(minutes: duration.inMinutes)) .inSeconds .toString() .padLeft(2, '0'); - final String videoLength = '$_totalHour$_totalMinute:$_totalSeconds'; + final String videoLength = '$totalHour$totalMinute:$totalSeconds'; return videoLength; } } diff --git a/lib/src/controllers/pod_video_quality_controller.dart b/lib/src/controllers/pod_video_quality_controller.dart index 9f4b8dee..f841e018 100644 --- a/lib/src/controllers/pod_video_quality_controller.dart +++ b/lib/src/controllers/pod_video_quality_controller.dart @@ -14,13 +14,19 @@ class _PodVideoQualityController extends _PodVideoController { ///*vimeo player configs /// ///get all `quality urls` - Future getQualityUrlsFromVimeoId(String videoId) async { + Future getQualityUrlsFromVimeoId( + String videoId, { + String? hash, + }) async { try { podVideoStateChanger(PodVideoState.loading); - final _vimeoVideoUrls = await VideoApis.getVimeoVideoQualityUrls(videoId); + final vimeoVideoUrls = await VideoApis.getVimeoVideoQualityUrls( + videoId, + hash, + ); /// - vimeoOrVideoUrls = _vimeoVideoUrls ?? []; + vimeoOrVideoUrls = vimeoVideoUrls ?? []; } catch (e) { rethrow; } @@ -32,11 +38,11 @@ class _PodVideoQualityController extends _PodVideoController { ) async { try { podVideoStateChanger(PodVideoState.loading); - final _vimeoVideoUrls = + final vimeoVideoUrls = await VideoApis.getVimeoPrivateVideoQualityUrls(videoId, httpHeader); /// - vimeoOrVideoUrls = _vimeoVideoUrls ?? []; + vimeoOrVideoUrls = vimeoVideoUrls ?? []; } catch (e) { rethrow; } @@ -45,21 +51,21 @@ class _PodVideoQualityController extends _PodVideoController { void sortQualityVideoUrls( List? urls, ) { - final _urls = urls; + final urls0 = urls; ///has issues with 240p - _urls?.removeWhere((element) => element.quality == 240); + urls0?.removeWhere((element) => element.quality == 240); ///has issues with 144p in web if (kIsWeb) { - _urls?.removeWhere((element) => element.quality == 144); + urls0?.removeWhere((element) => element.quality == 144); } ///sort - _urls?.sort((a, b) => a.quality.compareTo(b.quality)); + urls0?.sort((a, b) => a.quality.compareTo(b.quality)); /// - vimeoOrVideoUrls = _urls ?? []; + vimeoOrVideoUrls = urls0 ?? []; } ///get vimeo quality `ex: 1080p` url @@ -121,7 +127,7 @@ class _PodVideoQualityController extends _PodVideoController { podVideoStateChanger(PodVideoState.paused); podVideoStateChanger(PodVideoState.loading); playingVideoUrl = _videoQualityUrl; - _videoCtr = VideoPlayerController.network(_videoQualityUrl); + _videoCtr = VideoPlayerController.networkUrl(Uri.parse(_videoQualityUrl)); await _videoCtr?.initialize(); _videoDuration = _videoCtr?.value.duration ?? Duration.zero; _videoCtr?.addListener(videoListner); diff --git a/lib/src/models/play_video_from.dart b/lib/src/models/play_video_from.dart index 87849a61..d3927bbb 100644 --- a/lib/src/models/play_video_from.dart +++ b/lib/src/models/play_video_from.dart @@ -1,11 +1,14 @@ +import 'dart:io'; + import '../../pod_player.dart'; class PlayVideoFrom { final String? dataSource; + final String? hash; final PodVideoPlayerType playerType; final VideoFormat? formatHint; final String? package; - final dynamic file; + final File? file; final List? videoQualityUrls; final Future? closedCaptionFile; final VideoPlayerOptions? videoPlayerOptions; @@ -13,9 +16,10 @@ class PlayVideoFrom { final bool live; const PlayVideoFrom._({ + required this.playerType, this.live = false, this.dataSource, - required this.playerType, + this.hash, this.formatHint, this.package, this.file, @@ -60,7 +64,7 @@ class PlayVideoFrom { ///File Doesnot support web apps ///[file] is `File` Datatype import it from `dart:io` factory PlayVideoFrom.file( - dynamic file, { + File file, { Future? closedCaptionFile, VideoPlayerOptions? videoPlayerOptions, }) { @@ -74,6 +78,7 @@ class PlayVideoFrom { factory PlayVideoFrom.vimeo( String dataSource, { + String? hash, VideoFormat? formatHint, Future? closedCaptionFile, VideoPlayerOptions? videoPlayerOptions, @@ -82,6 +87,7 @@ class PlayVideoFrom { return PlayVideoFrom._( playerType: PodVideoPlayerType.vimeo, dataSource: dataSource, + hash: hash, formatHint: formatHint, closedCaptionFile: closedCaptionFile, videoPlayerOptions: videoPlayerOptions, diff --git a/lib/src/pod_player.dart b/lib/src/pod_player.dart index 1148d485..82f451f8 100644 --- a/lib/src/pod_player.dart +++ b/lib/src/pod_player.dart @@ -1,16 +1,15 @@ import 'dart:async'; -import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; -import 'package:lottie/lottie.dart'; -import 'package:universal_html/html.dart' as _html; +import 'package:universal_html/html.dart' as uni_html; import '../pod_player.dart'; import 'controllers/pod_getx_video_controller.dart'; import 'utils/logger.dart'; +import 'widgets/double_tap_icon.dart'; import 'widgets/material_icon_button.dart'; part 'widgets/animated_play_pause_icon.dart'; @@ -57,8 +56,8 @@ class PodVideoPlayer extends StatefulWidget { final WidgetBuilder? onLoading; PodVideoPlayer({ - Key? key, required this.controller, + super.key, this.frameAspectRatio = 16 / 9, this.videoAspectRatio = 16 / 9, this.alwaysShowProgressBar = true, @@ -73,7 +72,7 @@ class PodVideoPlayer extends StatefulWidget { this.videoThumbnail, this.onToggleFullScreen, this.onLoading, - }) : super(key: key) { + }) { addToUiController(); } @@ -121,7 +120,7 @@ class _PodVideoPlayerState extends State _podCtr.keyboardFocusWeb?.addListener(_podCtr.keyboadListner); } //to disable mouse right click - _html.document.onContextMenu.listen((event) => event.preventDefault()); + uni_html.document.onContextMenu.listen((event) => event.preventDefault()); } } @@ -159,7 +158,7 @@ class _PodVideoPlayerState extends State final circularProgressIndicator = _thumbnailAndLoadingWidget(); _podCtr.mainContext = context; - final _videoErrorWidget = AspectRatio( + final videoErrorWidget = AspectRatio( aspectRatio: _frameAspectRatio, child: Center( child: Column( @@ -191,15 +190,15 @@ class _PodVideoPlayerState extends State child: GetBuilder( tag: widget.controller.getTag, id: 'errorState', - builder: (_podCtr) { + builder: (podCtr) { /// Check if has any error - if (_podCtr.podVideoState == PodVideoState.error) { - return widget.onVideoError?.call() ?? _videoErrorWidget; + if (podCtr.podVideoState == PodVideoState.error) { + return widget.onVideoError?.call() ?? videoErrorWidget; } return AspectRatio( aspectRatio: _frameAspectRatio, - child: _podCtr.videoCtr?.value.isInitialized ?? false + child: podCtr.videoCtr?.value.isInitialized ?? false ? _buildPlayer() : Center(child: circularProgressIndicator), ); @@ -244,18 +243,18 @@ class _PodVideoPlayerState extends State } Widget _buildPlayer() { - final _videoAspectRatio = widget.matchVideoAspectRatioToFrame + final videoAspectRatio = widget.matchVideoAspectRatioToFrame ? _podCtr.videoCtr?.value.aspectRatio ?? widget.videoAspectRatio : widget.videoAspectRatio; if (kIsWeb) { return GetBuilder( tag: widget.controller.getTag, id: 'full-screen', - builder: (_podCtr) { - if (_podCtr.isFullScreen) return _thumbnailAndLoadingWidget(); + builder: (podCtr) { + if (podCtr.isFullScreen) return _thumbnailAndLoadingWidget(); return _PodCoreVideoPlayer( - videoPlayerCtr: _podCtr.videoCtr!, - videoAspectRatio: _videoAspectRatio, + videoPlayerCtr: podCtr.videoCtr!, + videoAspectRatio: videoAspectRatio, tag: widget.controller.getTag, ); }, @@ -263,7 +262,7 @@ class _PodVideoPlayerState extends State } else { return _PodCoreVideoPlayer( videoPlayerCtr: _podCtr.videoCtr!, - videoAspectRatio: _videoAspectRatio, + videoAspectRatio: videoAspectRatio, tag: widget.controller.getTag, ); } diff --git a/lib/src/utils/video_apis.dart b/lib/src/utils/video_apis.dart index e319469e..69153a19 100644 --- a/lib/src/utils/video_apis.dart +++ b/lib/src/utils/video_apis.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; +import 'package:http/http.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; import '../models/vimeo_models.dart'; @@ -11,24 +12,61 @@ String podErrorString(String val) { } class VideoApis { + static Future _makeRequestHash(String videoId, String? hash) { + if (hash == null) { + return http.get( + Uri.parse('https://player.vimeo.com/video/$videoId/config'), + ); + } else { + return http.get( + Uri.parse('https://player.vimeo.com/video/$videoId/config?h=$hash'), + ); + } + } + static Future?> getVimeoVideoQualityUrls( String videoId, + String? hash, ) async { try { - final response = await http.get( - Uri.parse('https://player.vimeo.com/video/$videoId/config'), - ); - final jsonData = - jsonDecode(response.body)['request']['files']['progressive']; - return List.generate( - jsonData.length, - (index) => VideoQalityUrls( - quality: int.parse( - (jsonData[index]['quality'] as String?)?.split('p').first ?? '0', + final response = await _makeRequestHash(videoId, hash); + final jsonData = jsonDecode(response.body)['request']['files']; + final dashData = jsonData['dash']; + final hlsData = jsonData['hls']; + final defaultCDN = hlsData['default_cdn']; + final cdnVideoUrl = (hlsData['cdns'][defaultCDN]['url'] as String?) ?? ''; + final List rawStreamUrls = + (dashData['streams'] as List?) ?? []; + + final List vimeoQualityUrls = []; + + for (final item in rawStreamUrls) { + final sepList = cdnVideoUrl.split('/sep/video/'); + final firstUrlPiece = sepList.firstOrNull ?? ''; + final lastUrlPiece = + ((sepList.lastOrNull ?? '').split('/').lastOrNull) ?? + (sepList.lastOrNull ?? ''); + final String urlId = + ((item['id'] ?? '') as String).split('-').firstOrNull ?? ''; + vimeoQualityUrls.add( + VideoQalityUrls( + quality: int.parse( + (item['quality'] as String?)?.split('p').first ?? '0', + ), + url: '$firstUrlPiece/sep/video/$urlId/$lastUrlPiece', ), - url: jsonData[index]['url'], - ), - ); + ); + } + if (vimeoQualityUrls.isEmpty) { + vimeoQualityUrls.add( + VideoQalityUrls( + quality: 720, + url: cdnVideoUrl, + ), + ); + } + + return vimeoQualityUrls; } catch (error) { if (error.toString().contains('XMLHttpRequest')) { log( @@ -51,7 +89,8 @@ class VideoApis { Uri.parse('https://api.vimeo.com/videos/$videoId'), headers: httpHeader, ); - final jsonData = jsonDecode(response.body)['files']; + final jsonData = + (jsonDecode(response.body)['files'] as List?) ?? []; final List list = []; for (int i = 0; i < jsonData.length; i++) { @@ -59,7 +98,12 @@ class VideoApis { (jsonData[i]['rendition'] as String?)?.split('p').first ?? '0'; final int? number = int.tryParse(quality); if (number != null && number != 0) { - list.add(VideoQalityUrls(quality: number, url: jsonData[i]['link'])); + list.add( + VideoQalityUrls( + quality: number, + url: jsonData[i]['link'] as String, + ), + ); } } return list; diff --git a/lib/src/widgets/animated_play_pause_icon.dart b/lib/src/widgets/animated_play_pause_icon.dart index 7c032ccd..0aabdd3a 100644 --- a/lib/src/widgets/animated_play_pause_icon.dart +++ b/lib/src/widgets/animated_play_pause_icon.dart @@ -5,10 +5,9 @@ class _AnimatedPlayPauseIcon extends StatefulWidget { final String tag; const _AnimatedPlayPauseIcon({ - Key? key, - this.size, required this.tag, - }) : super(key: key); + this.size, + }); @override State<_AnimatedPlayPauseIcon> createState() => _AnimatedPlayPauseIconState(); @@ -55,35 +54,35 @@ class _AnimatedPlayPauseIconState extends State<_AnimatedPlayPauseIcon> return GetBuilder( tag: widget.tag, id: 'overlay', - builder: (_podCtr) { + builder: (podCtr) { return GetBuilder( tag: widget.tag, id: 'podVideoState', - builder: (_f) => MaterialIconButton( - toolTipMesg: _f.isvideoPlaying - ? _podCtr.podPlayerLabels.pause ?? + builder: (f) => MaterialIconButton( + toolTipMesg: f.isvideoPlaying + ? podCtr.podPlayerLabels.pause ?? 'Pause${kIsWeb ? ' (space)' : ''}' - : _podCtr.podPlayerLabels.play ?? + : podCtr.podPlayerLabels.play ?? 'Play${kIsWeb ? ' (space)' : ''}', onPressed: - _podCtr.isOverlayVisible ? _podCtr.togglePlayPauseVideo : null, - child: onStateChange(_podCtr), + podCtr.isOverlayVisible ? podCtr.togglePlayPauseVideo : null, + child: onStateChange(podCtr), ), ); }, ); } - Widget onStateChange(PodGetXVideoController _podCtr) { - if (kIsWeb) return _playPause(_podCtr); - if (_podCtr.podVideoState == PodVideoState.loading) { + Widget onStateChange(PodGetXVideoController podCtr) { + if (kIsWeb) return _playPause(podCtr); + if (podCtr.podVideoState == PodVideoState.loading) { return const SizedBox(); } else { - return _playPause(_podCtr); + return _playPause(podCtr); } } - Widget _playPause(PodGetXVideoController _podCtr) { + Widget _playPause(PodGetXVideoController podCtr) { return AnimatedIcon( icon: AnimatedIcons.play_pause, progress: _payCtr, diff --git a/lib/src/widgets/core/overlays/mobile_bottomsheet.dart b/lib/src/widgets/core/overlays/mobile_bottomsheet.dart index 587ec96f..5cf988e9 100644 --- a/lib/src/widgets/core/overlays/mobile_bottomsheet.dart +++ b/lib/src/widgets/core/overlays/mobile_bottomsheet.dart @@ -4,26 +4,25 @@ class _MobileBottomSheet extends StatelessWidget { final String tag; const _MobileBottomSheet({ - Key? key, required this.tag, - }) : super(key: key); + }); @override Widget build(BuildContext context) { return GetBuilder( tag: tag, - builder: (_podCtr) => Column( + builder: (podCtr) => Column( mainAxisSize: MainAxisSize.min, children: [ - if (_podCtr.vimeoOrVideoUrls.isNotEmpty) + if (podCtr.vimeoOrVideoUrls.isNotEmpty) _bottomSheetTiles( - title: _podCtr.podPlayerLabels.quality, + title: podCtr.podPlayerLabels.quality, icon: Icons.video_settings_rounded, - subText: '${_podCtr.vimeoPlayingVideoQuality}p', + subText: '${podCtr.vimeoPlayingVideoQuality}p', onTap: () { Navigator.of(context).pop(); Timer(const Duration(milliseconds: 100), () { - showModalBottomSheet( + showModalBottomSheet( context: context, builder: (context) => SafeArea( child: _VideoQualitySelectorMob( @@ -39,24 +38,24 @@ class _MobileBottomSheet extends StatelessWidget { }, ), _bottomSheetTiles( - title: _podCtr.podPlayerLabels.loopVideo, + title: podCtr.podPlayerLabels.loopVideo, icon: Icons.loop_rounded, - subText: _podCtr.isLooping - ? _podCtr.podPlayerLabels.optionEnabled - : _podCtr.podPlayerLabels.optionDisabled, + subText: podCtr.isLooping + ? podCtr.podPlayerLabels.optionEnabled + : podCtr.podPlayerLabels.optionDisabled, onTap: () { Navigator.of(context).pop(); - _podCtr.toggleLooping(); + podCtr.toggleLooping(); }, ), _bottomSheetTiles( - title: _podCtr.podPlayerLabels.playbackSpeed, + title: podCtr.podPlayerLabels.playbackSpeed, icon: Icons.slow_motion_video_rounded, - subText: _podCtr.currentPaybackSpeed, + subText: podCtr.currentPaybackSpeed, onTap: () { Navigator.of(context).pop(); Timer(const Duration(milliseconds: 100), () { - showModalBottomSheet( + showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => SafeArea( @@ -121,25 +120,24 @@ class _VideoQualitySelectorMob extends StatelessWidget { final String tag; const _VideoQualitySelectorMob({ - Key? key, required this.onTap, required this.tag, - }) : super(key: key); + }); @override Widget build(BuildContext context) { - final _podCtr = Get.find(tag: tag); + final podCtr = Get.find(tag: tag); return SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, - children: _podCtr.vimeoOrVideoUrls + children: podCtr.vimeoOrVideoUrls .map( (e) => ListTile( title: Text('${e.quality}p'), onTap: () { onTap != null ? onTap!() : Navigator.of(context).pop(); - _podCtr.changeVideoQuality(e.quality); + podCtr.changeVideoQuality(e.quality); }, ), ) @@ -154,24 +152,23 @@ class _VideoPlaybackSelectorMob extends StatelessWidget { final String tag; const _VideoPlaybackSelectorMob({ - Key? key, required this.onTap, required this.tag, - }) : super(key: key); + }); @override Widget build(BuildContext context) { - final _podCtr = Get.find(tag: tag); + final podCtr = Get.find(tag: tag); return SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, - children: _podCtr.videoPlaybackSpeeds + children: podCtr.videoPlaybackSpeeds .map( (e) => ListTile( title: Text(e), onTap: () { onTap != null ? onTap!() : Navigator.of(context).pop(); - _podCtr.setVideoPlayBack(e); + podCtr.setVideoPlayBack(e); }, ), ) @@ -185,9 +182,8 @@ class _MobileOverlayBottomControlles extends StatelessWidget { final String tag; const _MobileOverlayBottomControlles({ - Key? key, required this.tag, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -197,7 +193,7 @@ class _MobileOverlayBottomControlles extends StatelessWidget { return GetBuilder( tag: tag, id: 'full-screen', - builder: (_podCtr) => Column( + builder: (podCtr) => Column( mainAxisSize: MainAxisSize.min, children: [ Row( @@ -206,11 +202,11 @@ class _MobileOverlayBottomControlles extends StatelessWidget { GetBuilder( tag: tag, id: 'video-progress', - builder: (_podCtr) { + builder: (podCtr) { return Row( children: [ Text( - _podCtr.calculateVideoDuration(_podCtr.videoPosition), + podCtr.calculateVideoDuration(podCtr.videoPosition), style: const TextStyle(color: itemColor), ), const Text( @@ -218,7 +214,7 @@ class _MobileOverlayBottomControlles extends StatelessWidget { style: durationTextStyle, ), Text( - _podCtr.calculateVideoDuration(_podCtr.videoDuration), + podCtr.calculateVideoDuration(podCtr.videoDuration), style: durationTextStyle, ), ], @@ -227,25 +223,25 @@ class _MobileOverlayBottomControlles extends StatelessWidget { ), const Spacer(), MaterialIconButton( - toolTipMesg: _podCtr.isFullScreen - ? _podCtr.podPlayerLabels.exitFullScreen ?? + toolTipMesg: podCtr.isFullScreen + ? podCtr.podPlayerLabels.exitFullScreen ?? 'Exit full screen${kIsWeb ? ' (f)' : ''}' - : _podCtr.podPlayerLabels.fullscreen ?? + : podCtr.podPlayerLabels.fullscreen ?? 'Fullscreen${kIsWeb ? ' (f)' : ''}', color: itemColor, onPressed: () { - if (_podCtr.isOverlayVisible) { - if (_podCtr.isFullScreen) { - _podCtr.disableFullScreen(context, tag); + if (podCtr.isOverlayVisible) { + if (podCtr.isFullScreen) { + podCtr.disableFullScreen(context, tag); } else { - _podCtr.enableFullScreen(tag); + podCtr.enableFullScreen(tag); } } else { - _podCtr.toggleVideoOverlay(); + podCtr.toggleVideoOverlay(); } }, child: Icon( - _podCtr.isFullScreen + podCtr.isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen, ), @@ -255,16 +251,16 @@ class _MobileOverlayBottomControlles extends StatelessWidget { GetBuilder( tag: tag, id: 'overlay', - builder: (_podCtr) { - if (_podCtr.isFullScreen) { + builder: (podCtr) { + if (podCtr.isFullScreen) { return Padding( padding: const EdgeInsets.fromLTRB(12, 0, 12, 20), child: Visibility( - visible: _podCtr.isOverlayVisible, + visible: podCtr.isOverlayVisible, child: PodProgressBar( tag: tag, alignment: Alignment.topCenter, - podProgressBarConfig: _podCtr.podProgressBarConfig, + podProgressBarConfig: podCtr.podProgressBarConfig, ), ), ); @@ -272,7 +268,7 @@ class _MobileOverlayBottomControlles extends StatelessWidget { return PodProgressBar( tag: tag, alignment: Alignment.bottomCenter, - podProgressBarConfig: _podCtr.podProgressBarConfig, + podProgressBarConfig: podCtr.podProgressBarConfig, ); }, ), diff --git a/lib/src/widgets/core/overlays/mobile_overlay.dart b/lib/src/widgets/core/overlays/mobile_overlay.dart index d38bb3d5..1afe558d 100644 --- a/lib/src/widgets/core/overlays/mobile_overlay.dart +++ b/lib/src/widgets/core/overlays/mobile_overlay.dart @@ -4,63 +4,52 @@ class _MobileOverlay extends StatelessWidget { final String tag; const _MobileOverlay({ - Key? key, required this.tag, - }) : super(key: key); + }); @override Widget build(BuildContext context) { const overlayColor = Colors.black38; const itemColor = Colors.white; - final _podCtr = Get.find(tag: tag); + final podCtr = Get.find(tag: tag); return Stack( alignment: Alignment.center, children: [ - Row( - children: [ - Expanded( - child: _VideoGestureDetector( - tag: tag, - onDoubleTap: _isRtl() - ? _podCtr.onRightDoubleTap - : _podCtr.onLeftDoubleTap, - child: ColoredBox( - color: overlayColor, - child: _LeftRightDoubleTapBox( + _VideoGestureDetector( + tag: tag, + child: ColoredBox( + color: overlayColor, + child: Row( + children: [ + Expanded( + child: DoubleTapIcon( tag: tag, - isLeft: !_isRtl(), + isForward: false, + height: double.maxFinite, + onDoubleTap: _isRtl() + ? podCtr.onRightDoubleTap + : podCtr.onLeftDoubleTap, ), ), - ), - ), - _VideoGestureDetector( - tag: tag, - child: ColoredBox( - color: overlayColor, - child: SizedBox( + SizedBox( height: double.infinity, child: Center( child: _AnimatedPlayPauseIcon(tag: tag, size: 42), ), ), - ), - ), - Expanded( - child: _VideoGestureDetector( - tag: tag, - onDoubleTap: _isRtl() - ? _podCtr.onLeftDoubleTap - : _podCtr.onRightDoubleTap, - child: ColoredBox( - color: overlayColor, - child: _LeftRightDoubleTapBox( + Expanded( + child: DoubleTapIcon( + isForward: true, tag: tag, - isLeft: _isRtl(), + height: double.maxFinite, + onDoubleTap: _isRtl() + ? podCtr.onLeftDoubleTap + : podCtr.onRightDoubleTap, ), ), - ), + ], ), - ], + ), ), Align( alignment: Alignment.topCenter, @@ -69,17 +58,17 @@ class _MobileOverlay extends StatelessWidget { children: [ Expanded( child: IgnorePointer( - child: _podCtr.videoTitle ?? const SizedBox(), + child: podCtr.videoTitle ?? const SizedBox(), ), ), MaterialIconButton( - toolTipMesg: _podCtr.podPlayerLabels.settings, + toolTipMesg: podCtr.podPlayerLabels.settings, color: itemColor, onPressed: () { - if (_podCtr.isOverlayVisible) { + if (podCtr.isOverlayVisible) { _bottomSheet(context); } else { - _podCtr.toggleVideoOverlay(); + podCtr.toggleVideoOverlay(); } }, child: const Icon( @@ -98,7 +87,7 @@ class _MobileOverlay extends StatelessWidget { } bool _isRtl() { - final Locale locale = window.locale; + final Locale locale = WidgetsBinding.instance.platformDispatcher.locale; final langs = [ 'ar', // Arabic 'fa', // Farsi @@ -116,66 +105,9 @@ class _MobileOverlay extends StatelessWidget { } void _bottomSheet(BuildContext context) { - showModalBottomSheet( + showModalBottomSheet( context: context, builder: (context) => SafeArea(child: _MobileBottomSheet(tag: tag)), ); } } - -class _LeftRightDoubleTapBox extends StatelessWidget { - final String tag; - final bool isLeft; - const _LeftRightDoubleTapBox({ - Key? key, - required this.tag, - required this.isLeft, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return GetBuilder( - tag: tag, - id: 'double-tap', - builder: (_podCtr) { - return SizedBox( - width: double.infinity, - height: double.infinity, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 200), - opacity: _podCtr.isLeftDbTapIconVisible && isLeft - ? 1 - : _podCtr.isRightDbTapIconVisible && !isLeft - ? 1 - : 0, - child: Center( - child: Stack( - alignment: Alignment.center, - children: [ - Lottie.asset( - isLeft - ? 'packages/pod_player/assets/forward_left.json' - : 'packages/pod_player/assets/forward_right.json', - ), - if (isLeft - ? _podCtr.isLeftDbTapIconVisible - : _podCtr.isRightDbTapIconVisible) - Transform.translate( - offset: const Offset(0, 40), - child: Text( - '${_podCtr.isLeftDbTapIconVisible ? _podCtr.leftDoubleTapduration : _podCtr.rightDubleTapduration} Sec', - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - ) - ], - ), - ), - ), - ); - }, - ); - } -} diff --git a/lib/src/widgets/core/overlays/overlays.dart b/lib/src/widgets/core/overlays/overlays.dart index ae0bbdf5..d634c609 100644 --- a/lib/src/widgets/core/overlays/overlays.dart +++ b/lib/src/widgets/core/overlays/overlays.dart @@ -4,41 +4,40 @@ class _VideoOverlays extends StatelessWidget { final String tag; const _VideoOverlays({ - Key? key, required this.tag, - }) : super(key: key); + }); @override Widget build(BuildContext context) { - final _podCtr = Get.find(tag: tag); - if (_podCtr.overlayBuilder != null) { + final podCtr = Get.find(tag: tag); + if (podCtr.overlayBuilder != null) { return GetBuilder( id: 'update-all', tag: tag, - builder: (_podCtr) { + builder: (podCtr) { ///Custom overlay - final _progressBar = PodProgressBar( + final progressBar = PodProgressBar( tag: tag, - podProgressBarConfig: _podCtr.podProgressBarConfig, + podProgressBarConfig: podCtr.podProgressBarConfig, ); final overlayOptions = OverLayOptions( - podVideoState: _podCtr.podVideoState, - videoDuration: _podCtr.videoDuration, - videoPosition: _podCtr.videoPosition, - isFullScreen: _podCtr.isFullScreen, - isLooping: _podCtr.isLooping, - isOverlayVisible: _podCtr.isOverlayVisible, - isMute: _podCtr.isMute, - autoPlay: _podCtr.autoPlay, - currentVideoPlaybackSpeed: _podCtr.currentPaybackSpeed, - videoPlayBackSpeeds: _podCtr.videoPlaybackSpeeds, - videoPlayerType: _podCtr.videoPlayerType, - podProgresssBar: _progressBar, + podVideoState: podCtr.podVideoState, + videoDuration: podCtr.videoDuration, + videoPosition: podCtr.videoPosition, + isFullScreen: podCtr.isFullScreen, + isLooping: podCtr.isLooping, + isOverlayVisible: podCtr.isOverlayVisible, + isMute: podCtr.isMute, + autoPlay: podCtr.autoPlay, + currentVideoPlaybackSpeed: podCtr.currentPaybackSpeed, + videoPlayBackSpeeds: podCtr.videoPlaybackSpeeds, + videoPlayerType: podCtr.videoPlayerType, + podProgresssBar: progressBar, ); /// Returns the custom overlay, otherwise returns the default /// overlay with gesture detector - return _podCtr.overlayBuilder!(overlayOptions); + return podCtr.overlayBuilder!(overlayOptions); }, ); } else { @@ -46,10 +45,10 @@ class _VideoOverlays extends StatelessWidget { return GetBuilder( tag: tag, id: 'overlay', - builder: (_podCtr) { + builder: (podCtr) { return AnimatedOpacity( duration: const Duration(milliseconds: 200), - opacity: _podCtr.isOverlayVisible ? 1 : 0, + opacity: podCtr.isOverlayVisible ? 1 : 0, child: Stack( fit: StackFit.passthrough, children: [ diff --git a/lib/src/widgets/core/overlays/web_dropdown_menu.dart b/lib/src/widgets/core/overlays/web_dropdown_menu.dart index 0d62564f..78965f89 100644 --- a/lib/src/widgets/core/overlays/web_dropdown_menu.dart +++ b/lib/src/widgets/core/overlays/web_dropdown_menu.dart @@ -4,9 +4,8 @@ class _WebSettingsDropdown extends StatefulWidget { final String tag; const _WebSettingsDropdown({ - Key? key, required this.tag, - }) : super(key: key); + }); @override State<_WebSettingsDropdown> createState() => _WebSettingsDropdownState(); @@ -18,47 +17,46 @@ class _WebSettingsDropdownState extends State<_WebSettingsDropdown> { return Theme( data: Theme.of(context).copyWith( focusColor: Colors.white, - selectedRowColor: Colors.white, ), child: GetBuilder( tag: widget.tag, - builder: (_podCtr) { + builder: (podCtr) { return MaterialIconButton( - toolTipMesg: _podCtr.podPlayerLabels.settings, + toolTipMesg: podCtr.podPlayerLabels.settings, color: Colors.white, child: const Icon(Icons.settings), - onPressed: () => _podCtr.isFullScreen - ? _podCtr.isWebPopupOverlayOpen = true - : _podCtr.isWebPopupOverlayOpen = false, + onPressed: () => podCtr.isFullScreen + ? podCtr.isWebPopupOverlayOpen = true + : podCtr.isWebPopupOverlayOpen = false, onTapDown: (details) async { - final _settingsMenu = await showMenu( + final settingsMenu = await showMenu( context: context, items: [ - if (_podCtr.vimeoOrVideoUrls.isNotEmpty) + if (podCtr.vimeoOrVideoUrls.isNotEmpty) PopupMenuItem( value: 'OUALITY', child: _bottomSheetTiles( - title: _podCtr.podPlayerLabels.quality, + title: podCtr.podPlayerLabels.quality, icon: Icons.video_settings_rounded, - subText: '${_podCtr.vimeoPlayingVideoQuality}p', + subText: '${podCtr.vimeoPlayingVideoQuality}p', ), ), PopupMenuItem( value: 'LOOP', child: _bottomSheetTiles( - title: _podCtr.podPlayerLabels.loopVideo, + title: podCtr.podPlayerLabels.loopVideo, icon: Icons.loop_rounded, - subText: _podCtr.isLooping - ? _podCtr.podPlayerLabels.optionEnabled - : _podCtr.podPlayerLabels.optionDisabled, + subText: podCtr.isLooping + ? podCtr.podPlayerLabels.optionEnabled + : podCtr.podPlayerLabels.optionDisabled, ), ), PopupMenuItem( value: 'SPEED', child: _bottomSheetTiles( - title: _podCtr.podPlayerLabels.playbackSpeed, + title: podCtr.podPlayerLabels.playbackSpeed, icon: Icons.slow_motion_video_rounded, - subText: _podCtr.currentPaybackSpeed, + subText: podCtr.currentPaybackSpeed, ), ), ], @@ -67,19 +65,19 @@ class _WebSettingsDropdownState extends State<_WebSettingsDropdown> { MediaQuery.of(context).size, ), ); - switch (_settingsMenu) { + switch (settingsMenu) { case 'OUALITY': - await _onVimeoQualitySelect(details, _podCtr); + await _onVimeoQualitySelect(details, podCtr); break; case 'SPEED': - await _onPlaybackSpeedSelect(details, _podCtr); + await _onPlaybackSpeedSelect(details, podCtr); break; case 'LOOP': - _podCtr.isWebPopupOverlayOpen = false; - await _podCtr.toggleLooping(); + podCtr.isWebPopupOverlayOpen = false; + await podCtr.toggleLooping(); break; default: - _podCtr.isWebPopupOverlayOpen = false; + podCtr.isWebPopupOverlayOpen = false; } }, ); @@ -90,21 +88,21 @@ class _WebSettingsDropdownState extends State<_WebSettingsDropdown> { Future _onPlaybackSpeedSelect( TapDownDetails details, - PodGetXVideoController _podCtr, + PodGetXVideoController podCtr, ) async { - await Future.delayed( + await Future.delayed( const Duration(milliseconds: 400), ); await showMenu( context: context, - items: _podCtr.videoPlaybackSpeeds + items: podCtr.videoPlaybackSpeeds .map( - (e) => PopupMenuItem( + (e) => PopupMenuItem( child: ListTile( title: Text(e), ), onTap: () { - _podCtr.setVideoPlayBack(e); + podCtr.setVideoPlayBack(e); }, ), ) @@ -115,26 +113,26 @@ class _WebSettingsDropdownState extends State<_WebSettingsDropdown> { MediaQuery.of(context).size, ), ); - _podCtr.isWebPopupOverlayOpen = false; + podCtr.isWebPopupOverlayOpen = false; } Future _onVimeoQualitySelect( TapDownDetails details, - PodGetXVideoController _podCtr, + PodGetXVideoController podCtr, ) async { - await Future.delayed( + await Future.delayed( const Duration(milliseconds: 400), ); await showMenu( context: context, - items: _podCtr.vimeoOrVideoUrls + items: podCtr.vimeoOrVideoUrls .map( - (e) => PopupMenuItem( + (e) => PopupMenuItem( child: ListTile( title: Text('${e.quality}p'), ), onTap: () { - _podCtr.changeVideoQuality( + podCtr.changeVideoQuality( e.quality, ); }, @@ -147,7 +145,7 @@ class _WebSettingsDropdownState extends State<_WebSettingsDropdown> { MediaQuery.of(context).size, ), ); - _podCtr.isWebPopupOverlayOpen = false; + podCtr.isWebPopupOverlayOpen = false; } Widget _bottomSheetTiles({ diff --git a/lib/src/widgets/core/overlays/web_overlay.dart b/lib/src/widgets/core/overlays/web_overlay.dart index cce58a60..3371b2fc 100644 --- a/lib/src/widgets/core/overlays/web_overlay.dart +++ b/lib/src/widgets/core/overlays/web_overlay.dart @@ -3,21 +3,20 @@ part of 'package:pod_player/src/pod_player.dart'; class _WebOverlay extends StatelessWidget { final String tag; const _WebOverlay({ - Key? key, required this.tag, - }) : super(key: key); + }); @override Widget build(BuildContext context) { const overlayColor = Colors.black38; - final _podCtr = Get.find(tag: tag); + final podCtr = Get.find(tag: tag); return Stack( children: [ Positioned.fill( child: _VideoGestureDetector( tag: tag, - onTap: _podCtr.togglePlayPauseVideo, - onDoubleTap: () => _podCtr.toggleFullScreenOnWeb(context, tag), + onTap: podCtr.togglePlayPauseVideo, + onDoubleTap: () => podCtr.toggleFullScreenOnWeb(context, tag), child: const ColoredBox( color: overlayColor, child: SizedBox.expand(), @@ -31,28 +30,38 @@ class _WebOverlay extends StatelessWidget { ), ), Positioned.fill( - child: Row( - children: [ - Expanded( - child: IgnorePointer( - child: _LeftRightDoubleTapBox( - tag: tag, - isLeft: true, + child: GetBuilder( + tag: tag, + id: 'double-tap', + builder: (podCtr) { + return Row( + children: [ + Expanded( + child: IgnorePointer( + child: DoubleTapIcon( + onDoubleTap: () {}, + tag: tag, + isForward: false, + iconOnly: true, + ), + ), ), - ), - ), - Expanded( - child: IgnorePointer( - child: _LeftRightDoubleTapBox( - tag: tag, - isLeft: false, + Expanded( + child: IgnorePointer( + child: DoubleTapIcon( + onDoubleTap: () {}, + tag: tag, + isForward: true, + iconOnly: true, + ), + ), ), - ), - ), - ], + ], + ); + }, ), ), - IgnorePointer(child: _podCtr.videoTitle ?? const SizedBox()), + IgnorePointer(child: podCtr.videoTitle ?? const SizedBox()), ], ); } @@ -62,19 +71,18 @@ class _WebOverlayBottomControlles extends StatelessWidget { final String tag; const _WebOverlayBottomControlles({ - Key? key, required this.tag, - }) : super(key: key); + }); @override Widget build(BuildContext context) { - final _podCtr = Get.find(tag: tag); + final podCtr = Get.find(tag: tag); const durationTextStyle = TextStyle(color: Colors.white70); const itemColor = Colors.white; return MouseRegion( - onHover: (event) => _podCtr.onOverlayHover(), - onExit: (event) => _podCtr.onOverlayHoverExit(), + onHover: (event) => podCtr.onOverlayHover(), + onExit: (event) => podCtr.onOverlayHoverExit(), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10), child: Column( @@ -82,7 +90,7 @@ class _WebOverlayBottomControlles extends StatelessWidget { children: [ PodProgressBar( tag: tag, - podProgressBarConfig: _podCtr.podProgressBarConfig, + podProgressBarConfig: podCtr.podProgressBarConfig, ), Row( children: [ @@ -97,16 +105,16 @@ class _WebOverlayBottomControlles extends StatelessWidget { GetBuilder( tag: tag, id: 'volume', - builder: (_podCtr) => MaterialIconButton( - toolTipMesg: _podCtr.isMute - ? _podCtr.podPlayerLabels.unmute ?? + builder: (podCtr) => MaterialIconButton( + toolTipMesg: podCtr.isMute + ? podCtr.podPlayerLabels.unmute ?? 'Unmute${kIsWeb ? ' (m)' : ''}' - : _podCtr.podPlayerLabels.mute ?? + : podCtr.podPlayerLabels.mute ?? 'Mute${kIsWeb ? ' (m)' : ''}', color: itemColor, - onPressed: _podCtr.toggleMute, + onPressed: podCtr.toggleMute, child: Icon( - _podCtr.isMute + podCtr.isMute ? Icons.volume_off_rounded : Icons.volume_up_rounded, ), @@ -115,12 +123,12 @@ class _WebOverlayBottomControlles extends StatelessWidget { GetBuilder( tag: tag, id: 'video-progress', - builder: (_podCtr) { + builder: (podCtr) { return Row( children: [ Text( - _podCtr.calculateVideoDuration( - _podCtr.videoPosition, + podCtr.calculateVideoDuration( + podCtr.videoPosition, ), style: durationTextStyle, ), @@ -129,8 +137,8 @@ class _WebOverlayBottomControlles extends StatelessWidget { style: durationTextStyle, ), Text( - _podCtr.calculateVideoDuration( - _podCtr.videoDuration, + podCtr.calculateVideoDuration( + podCtr.videoDuration, ), style: durationTextStyle, ), @@ -151,16 +159,15 @@ class _WebOverlayBottomControlles extends StatelessWidget { children: [ _WebSettingsDropdown(tag: tag), MaterialIconButton( - toolTipMesg: _podCtr.isFullScreen - ? _podCtr.podPlayerLabels.exitFullScreen ?? + toolTipMesg: podCtr.isFullScreen + ? podCtr.podPlayerLabels.exitFullScreen ?? 'Exit full screen${kIsWeb ? ' (f)' : ''}' - : _podCtr.podPlayerLabels.fullscreen ?? + : podCtr.podPlayerLabels.fullscreen ?? 'Fullscreen${kIsWeb ? ' (f)' : ''}', color: itemColor, - onPressed: () => - _onFullScreenToggle(_podCtr, context), + onPressed: () => _onFullScreenToggle(podCtr, context), child: Icon( - _podCtr.isFullScreen + podCtr.isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen, ), @@ -178,29 +185,29 @@ class _WebOverlayBottomControlles extends StatelessWidget { } void _onFullScreenToggle( - PodGetXVideoController _podCtr, + PodGetXVideoController podCtr, BuildContext context, ) { - if (_podCtr.isOverlayVisible) { - if (_podCtr.isFullScreen) { + if (podCtr.isOverlayVisible) { + if (podCtr.isFullScreen) { if (kIsWeb) { - _html.document.exitFullscreen(); - _podCtr.disableFullScreen(context, tag); + uni_html.document.exitFullscreen(); + podCtr.disableFullScreen(context, tag); return; } else { - _podCtr.disableFullScreen(context, tag); + podCtr.disableFullScreen(context, tag); } } else { if (kIsWeb) { - _html.document.documentElement?.requestFullscreen(); - _podCtr.enableFullScreen(tag); + uni_html.document.documentElement?.requestFullscreen(); + podCtr.enableFullScreen(tag); return; } else { - _podCtr.enableFullScreen(tag); + podCtr.enableFullScreen(tag); } } } else { - _podCtr.toggleVideoOverlay(); + podCtr.toggleVideoOverlay(); } } } diff --git a/lib/src/widgets/core/pod_core_player.dart b/lib/src/widgets/core/pod_core_player.dart index a40ffa22..7a7a3e47 100644 --- a/lib/src/widgets/core/pod_core_player.dart +++ b/lib/src/widgets/core/pod_core_player.dart @@ -6,25 +6,24 @@ class _PodCoreVideoPlayer extends StatelessWidget { final String tag; const _PodCoreVideoPlayer({ - Key? key, required this.videoPlayerCtr, required this.videoAspectRatio, required this.tag, - }) : super(key: key); + }); @override Widget build(BuildContext context) { - final _podCtr = Get.find(tag: tag); + final podCtr = Get.find(tag: tag); return Builder( - builder: (_ctrx) { + builder: (ctrx) { return RawKeyboardListener( autofocus: true, focusNode: - (_podCtr.isFullScreen ? FocusNode() : _podCtr.keyboardFocusWeb) ?? + (podCtr.isFullScreen ? FocusNode() : podCtr.keyboardFocusWeb) ?? FocusNode(), - onKey: (value) => _podCtr.onKeyBoardEvents( + onKey: (value) => podCtr.onKeyBoardEvents( event: value, - appContext: _ctrx, + appContext: ctrx, tag: tag, ), child: Stack( @@ -42,13 +41,13 @@ class _PodCoreVideoPlayer extends StatelessWidget { builder: (_) => GetBuilder( tag: tag, id: 'video-progress', - builder: (_podCtr) { - if (_podCtr.videoThumbnail == null) { + builder: (podCtr) { + if (podCtr.videoThumbnail == null) { return const SizedBox(); } - if (_podCtr.podVideoState == PodVideoState.paused && - _podCtr.videoPosition == Duration.zero) { + if (podCtr.podVideoState == PodVideoState.paused && + podCtr.videoPosition == Duration.zero) { return SizedBox.expand( child: TweenAnimationBuilder( builder: (context, value, child) => Opacity( @@ -59,7 +58,7 @@ class _PodCoreVideoPlayer extends StatelessWidget { duration: const Duration(milliseconds: 400), child: DecoratedBox( decoration: BoxDecoration( - image: _podCtr.videoThumbnail, + image: podCtr.videoThumbnail, ), ), ), @@ -74,8 +73,8 @@ class _PodCoreVideoPlayer extends StatelessWidget { child: GetBuilder( tag: tag, id: 'podVideoState', - builder: (_podCtr) { - final loadingWidget = _podCtr.onLoading?.call(context) ?? + builder: (podCtr) { + final loadingWidget = podCtr.onLoading?.call(context) ?? const Center( child: CircularProgressIndicator( backgroundColor: Colors.transparent, @@ -85,7 +84,7 @@ class _PodCoreVideoPlayer extends StatelessWidget { ); if (kIsWeb) { - switch (_podCtr.podVideoState) { + switch (podCtr.podVideoState) { case PodVideoState.loading: return loadingWidget; case PodVideoState.paused: @@ -116,7 +115,7 @@ class _PodCoreVideoPlayer extends StatelessWidget { return const SizedBox(); } } else { - if (_podCtr.podVideoState == PodVideoState.loading) { + if (podCtr.podVideoState == PodVideoState.loading) { return loadingWidget; } return const SizedBox(); @@ -128,13 +127,13 @@ class _PodCoreVideoPlayer extends StatelessWidget { GetBuilder( tag: tag, id: 'full-screen', - builder: (_podCtr) => _podCtr.isFullScreen + builder: (podCtr) => podCtr.isFullScreen ? const SizedBox() : GetBuilder( tag: tag, id: 'overlay', - builder: (_podCtr) => _podCtr.isOverlayVisible || - !_podCtr.alwaysShowProgressBar + builder: (podCtr) => podCtr.isOverlayVisible || + !podCtr.alwaysShowProgressBar ? const SizedBox() : Align( alignment: Alignment.bottomCenter, @@ -142,7 +141,7 @@ class _PodCoreVideoPlayer extends StatelessWidget { tag: tag, alignment: Alignment.bottomCenter, podProgressBarConfig: - _podCtr.podProgressBarConfig, + podCtr.podProgressBarConfig, ), ), ), diff --git a/lib/src/widgets/core/video_gesture_detector.dart b/lib/src/widgets/core/video_gesture_detector.dart index a6bf30f0..763582e5 100644 --- a/lib/src/widgets/core/video_gesture_detector.dart +++ b/lib/src/widgets/core/video_gesture_detector.dart @@ -7,21 +7,20 @@ class _VideoGestureDetector extends StatelessWidget { final String tag; const _VideoGestureDetector({ - Key? key, + required this.tag, this.child, this.onDoubleTap, this.onTap, - required this.tag, - }) : super(key: key); + }); @override Widget build(BuildContext context) { - final _podCtr = Get.find(tag: tag); + final podCtr = Get.find(tag: tag); return MouseRegion( - onHover: (event) => _podCtr.onOverlayHover(), - onExit: (event) => _podCtr.onOverlayHoverExit(), + onHover: (event) => podCtr.onOverlayHover(), + onExit: (event) => podCtr.onOverlayHoverExit(), child: GestureDetector( - onTap: onTap ?? _podCtr.toggleVideoOverlay, + onTap: onTap ?? podCtr.toggleVideoOverlay, onDoubleTap: onDoubleTap, child: child, ), diff --git a/lib/src/widgets/doubble_tap_effect.dart b/lib/src/widgets/doubble_tap_effect.dart new file mode 100644 index 00000000..f23915f9 --- /dev/null +++ b/lib/src/widgets/doubble_tap_effect.dart @@ -0,0 +1,238 @@ +import 'package:flutter/material.dart'; + +class DoubleTapRippleEffect extends StatefulWidget { + /// child widget [child] + final Widget? child; + + /// Helps to wrap child widget inside a parent widget + final Widget Function(Widget parentWidget, double curveRadius)? wrapper; + + /// touch effect color of widget [rippleColor] + final Color? rippleColor; + + /// TouchRippleEffect widget background color [backgroundColor] + final Color? backgroundColor; + + /// if you have border of child widget then you should apply [borderRadius] + final BorderRadius? borderRadius; + + /// animation duration of touch effect. [rippleDuration] + final Duration? rippleDuration; + + /// duration to stay the frame. [rippleEndingDuraiton] + final Duration? rippleEndingDuraiton; + + /// user click or tap handle [onDoubleTap]. + final void Function()? onDoubleTap; + + /// TouchRippleEffect widget width size [width] + final double? width; + + /// TouchRippleEffect widget height size [height] + final double? height; + + const DoubleTapRippleEffect({ + super.key, + this.child, + this.wrapper, + this.rippleColor, + this.backgroundColor, + this.borderRadius, + this.rippleDuration, + this.rippleEndingDuraiton, + this.onDoubleTap, + this.width, + this.height, + }); + + @override + State createState() => _DoubleTapRippleEffectState(); +} + +class _DoubleTapRippleEffectState extends State + with SingleTickerProviderStateMixin { + // by default offset will be 0,0 + // it will be set when user tap on widget + Offset _tapOffset = Offset.zero; + + // globalKey variable decleared + final GlobalKey _globalKey = GlobalKey(); + + // animation global variable decleared and + // type cast is double + late Animation _anim; + + // animation controller global variable decleared + late AnimationController _animationController; + + /// width of user child widget + double _mWidth = 0; + + // height of user child widget + double _mHeight = 0; + + // tween animation global variable decleared and + // type cast is double + late Tween _tweenAnim; + + // animation count of Tween anim. + // by default value is 0. + double _animRadiusValue = 0; + + @override + void initState() { + super.initState(); + // animation controller initialized + _animationController = AnimationController( + vsync: this, + duration: widget.rippleDuration ?? const Duration(milliseconds: 300), + ); + // animation controller listener added or iitialized + _animationController.addListener(_update); + } + + // update animation when started + + void _update() { + setState(() { + // [_anim.value] setting to [_animRadiusValue] global variable + _animRadiusValue = _anim.value; + }); + // animation status function calling + _animStatus(); + } + + // checking animation status is completed + void _animStatus() { + if (_anim.status == AnimationStatus.completed) { + Future.delayed( + widget.rippleEndingDuraiton ?? const Duration(milliseconds: 600), + ).then((value) { + setState(() { + _animRadiusValue = 0; + }); + // stoping animation after completed + _animationController.stop(); + }); + } + } + + @override + void dispose() { + // disposing [_animationController] when parent exist of close + _animationController.dispose(); + super.dispose(); + } + + // animation initialize reset and start + void _animate() { + final width = widget.width ?? _mWidth; + final height = widget.height ?? _mHeight; + // [Tween] animation initialize to global variable + _tweenAnim = Tween(begin: 0, end: (width + height) / 1.5); + + // adding [_animationController] to [_tweenanim] to animate + _anim = _tweenAnim.animate(_animationController); + + _animationController + // resetting [_animationController] before start + ..reset() + // starting [_animationController] to start animation + ..forward(); + } + + @override + Widget build(BuildContext context) { + final curveRadius = (_mWidth + _mHeight) / 2; + if (widget.wrapper != null) return widget.wrapper!(_builder(), curveRadius); + return _builder(); + } + + Widget _builder() { + return GestureDetector( + onDoubleTap: widget.onDoubleTap, + onDoubleTapDown: (details) { + // getting tap [localPostion] of user + final lp = details.localPosition; + setState(() { + /// setting [Offset] of user tap to [_tapOffset] global variable + _tapOffset = Offset(lp.dx, lp.dy); + }); + + // getting [size] of child widget + final size = _globalKey.currentContext!.size!; + + // child widget [width] initialize to [_width] global variable + _mWidth = size.width; + + // child widget [height] initialize to [_height] global variable + _mHeight = size.height; + + // starting animation + _animate(); + }, + child: Container( + width: widget.width, + height: widget.height, + + // added globalKey for getting child widget size + key: _globalKey, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + // when color == null then color will be transpatent otherwise color will be backgroundColor + color: widget.backgroundColor ?? Colors.transparent, + + // boderRadius of container if user passed + borderRadius: widget.borderRadius, + ), + child: Stack( + children: [ + // added child widget of user + widget.child!, + Opacity( + opacity: 0.3, + child: CustomPaint( + // ripplePainter is CustomPainer for circular ripple draw + painter: RipplePainer( + offset: _tapOffset, + circleRadius: _animRadiusValue, + fillColor: widget.rippleColor, + ), + ), + ) + ], + ), + ), + ); + } +} + +class RipplePainer extends CustomPainter { + // user tap locations [Offset] + final Offset? offset; + + // radius of circle which will be ripple color size [circleRadius] + final double? circleRadius; + + // fill color of ripple [fillColor] + final Color? fillColor; + RipplePainer({this.offset, this.circleRadius, this.fillColor}); + + @override + void paint(Canvas canvas, Size size) { + // throw an [rippleColor == null error] if ripple color is null + final paint = Paint() + ..color = fillColor == null + ? throw Exception('rippleColor of TouchRippleEffect == null') + : fillColor! + ..isAntiAlias = true; + + // drawing canvas based on user click offset,radius and paint + canvas.drawCircle(offset!, circleRadius!, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/src/widgets/double_tap_icon.dart b/lib/src/widgets/double_tap_icon.dart new file mode 100644 index 00000000..1875e920 --- /dev/null +++ b/lib/src/widgets/double_tap_icon.dart @@ -0,0 +1,186 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../controllers/pod_getx_video_controller.dart'; +import 'doubble_tap_effect.dart'; + +class DoubleTapIcon extends StatefulWidget { + final void Function() onDoubleTap; + final String tag; + final bool iconOnly; + final bool isForward; + final double height; + final double? width; + + const DoubleTapIcon({ + required this.onDoubleTap, + required this.tag, + required this.isForward, + super.key, + this.iconOnly = false, + this.height = 50, + this.width, + }); + + @override + State createState() => _DoubleTapIconState(); +} + +class _DoubleTapIconState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _animationController; + late final Animation opacityCtr; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 200), + ); + opacityCtr = Tween(begin: 0, end: 1).animate( + CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ), + ); + final podCtr = Get.find(tag: widget.tag); + if (widget.iconOnly && !widget.isForward) { + podCtr.addListenerId('double-tap-left', _onDoubleTap); + } + if (widget.iconOnly && widget.isForward) { + podCtr.addListenerId('double-tap-right', _onDoubleTap); + } + } + + @override + void dispose() { + final podCtr = Get.find(tag: widget.tag); + + if (widget.iconOnly && !widget.isForward) { + podCtr.removeListenerId('double-tap-left', _onDoubleTap); + } + if (widget.iconOnly && widget.isForward) { + podCtr.removeListenerId('double-tap-right', _onDoubleTap); + } + _animationController.dispose(); + super.dispose(); + } + + void _onDoubleTap() { + widget.onDoubleTap(); + _animationController.forward().then((_) { + _animationController.reverse(); + }); + } + + @override + Widget build(BuildContext context) { + if (widget.iconOnly) return iconWithText(); + return DoubleTapRippleEffect( + onDoubleTap: _onDoubleTap, + rippleColor: Colors.white, + wrapper: (parentWidget, curveRadius) { + final forwardRadius = + !widget.isForward ? Radius.zero : Radius.circular(curveRadius); + final backwardRadius = + widget.isForward ? Radius.zero : Radius.circular(curveRadius); + return ClipRRect( + borderRadius: BorderRadius.only( + bottomLeft: forwardRadius, + topLeft: forwardRadius, + bottomRight: backwardRadius, + topRight: backwardRadius, + ), + child: parentWidget, + ); + }, + child: iconWithText(), + ); + } + + SizedBox iconWithText() { + return SizedBox( + height: widget.height, + width: widget.width, + child: AnimatedBuilder( + animation: _animationController, + builder: (context, child) { + const icon = Icon( + Icons.play_arrow_sharp, + size: 32, + color: Colors.white, + ); + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RotatedBox( + quarterTurns: widget.isForward ? 0 : 2, + child: Stack( + children: [ + AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: opacityCtr.value, + child: icon, + ), + Padding( + padding: const EdgeInsets.only(left: 20), + child: AnimatedOpacity( + duration: const Duration(milliseconds: 300), + opacity: opacityCtr.value, + child: icon, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 40), + child: AnimatedOpacity( + duration: const Duration(milliseconds: 600), + opacity: opacityCtr.value, + child: icon, + ), + ), + ], + ), + ), + GetBuilder( + tag: widget.tag, + id: 'double-tap', + builder: (podCtr) { + if (widget.isForward && podCtr.isRightDbTapIconVisible) { + return AnimatedOpacity( + duration: const Duration(milliseconds: 300), + opacity: opacityCtr.value, + child: Text( + '${podCtr.isLeftDbTapIconVisible ? podCtr.leftDoubleTapduration : podCtr.rightDubleTapduration} Sec', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ); + } + if (!widget.isForward && podCtr.isLeftDbTapIconVisible) { + return AnimatedOpacity( + duration: const Duration(milliseconds: 300), + opacity: opacityCtr.value, + child: Text( + '${podCtr.isLeftDbTapIconVisible ? podCtr.leftDoubleTapduration : podCtr.rightDubleTapduration} Sec', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ); + } + return const SizedBox(); + }, + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/src/widgets/full_screen_view.dart b/lib/src/widgets/full_screen_view.dart index 98021ae0..e26501ac 100644 --- a/lib/src/widgets/full_screen_view.dart +++ b/lib/src/widgets/full_screen_view.dart @@ -3,9 +3,9 @@ part of 'package:pod_player/src/pod_player.dart'; class FullScreenView extends StatefulWidget { final String tag; const FullScreenView({ - Key? key, required this.tag, - }) : super(key: key); + super.key, + }); @override State createState() => _FullScreenViewState(); @@ -55,21 +55,21 @@ class _FullScreenViewState extends State backgroundColor: Colors.black, body: GetBuilder( tag: widget.tag, - builder: (_podCtr) => Center( + builder: (podCtr) => Center( child: ColoredBox( color: Colors.black, child: SizedBox( height: MediaQuery.of(context).size.height, width: MediaQuery.of(context).size.width, child: Center( - child: _podCtr.videoCtr == null + child: podCtr.videoCtr == null ? loadingWidget - : _podCtr.videoCtr!.value.isInitialized + : podCtr.videoCtr!.value.isInitialized ? _PodCoreVideoPlayer( tag: widget.tag, - videoPlayerCtr: _podCtr.videoCtr!, + videoPlayerCtr: podCtr.videoCtr!, videoAspectRatio: - _podCtr.videoCtr?.value.aspectRatio ?? 16 / 9, + podCtr.videoCtr?.value.aspectRatio ?? 16 / 9, ) : loadingWidget, ), diff --git a/lib/src/widgets/material_icon_button.dart b/lib/src/widgets/material_icon_button.dart index e80734d2..6dccb2c1 100644 --- a/lib/src/widgets/material_icon_button.dart +++ b/lib/src/widgets/material_icon_button.dart @@ -2,15 +2,15 @@ import 'package:flutter/material.dart'; class MaterialIconButton extends StatelessWidget { const MaterialIconButton({ - Key? key, - this.color, required this.child, - this.radius = 12, required this.toolTipMesg, + super.key, + this.color, + this.radius = 12, this.onPressed, this.onHover, this.onTapDown, - }) : super(key: key); + }); final Color? color; final Widget child; diff --git a/lib/src/widgets/pod_progress_bar.dart b/lib/src/widgets/pod_progress_bar.dart index 7706f61b..855180cd 100644 --- a/lib/src/widgets/pod_progress_bar.dart +++ b/lib/src/widgets/pod_progress_bar.dart @@ -9,21 +9,20 @@ import '../models/pod_progress_bar_config.dart'; /// Renders progress bar for the video using custom paint. class PodProgressBar extends StatefulWidget { const PodProgressBar({ - Key? key, + required this.tag, + super.key, PodProgressBarConfig? podProgressBarConfig, this.onDragStart, this.onDragEnd, this.onDragUpdate, this.alignment = Alignment.center, - required this.tag, - }) : podProgressBarConfig = - podProgressBarConfig ?? const PodProgressBarConfig(), - super(key: key); + }) : podProgressBarConfig = + podProgressBarConfig ?? const PodProgressBarConfig(); final PodProgressBarConfig podProgressBarConfig; - final Function()? onDragStart; - final Function()? onDragEnd; - final Function()? onDragUpdate; + final void Function()? onDragStart; + final void Function()? onDragEnd; + final void Function()? onDragUpdate; final Alignment alignment; final String tag; @@ -54,8 +53,8 @@ class _PodProgressBarState extends State { return GetBuilder( tag: widget.tag, id: 'video-progress', - builder: (_podCtr) { - videoPlayerValue = _podCtr.videoCtr?.value; + builder: (podCtr) { + videoPlayerValue = podCtr.videoCtr?.value; return LayoutBuilder( builder: (context, size) { return GestureDetector( @@ -66,9 +65,9 @@ class _PodProgressBarState extends State { return; } _controllerWasPlaying = - _podCtr.videoCtr?.value.isPlaying ?? false; + podCtr.videoCtr?.value.isPlaying ?? false; if (_controllerWasPlaying) { - _podCtr.videoCtr?.pause(); + podCtr.videoCtr?.pause(); } if (widget.onDragStart != null) { @@ -79,16 +78,16 @@ class _PodProgressBarState extends State { if (!videoPlayerValue!.isInitialized) { return; } - _podCtr.isShowOverlay(true); + podCtr.isShowOverlay(true); seekToRelativePosition(details.globalPosition); widget.onDragUpdate?.call(); }, onHorizontalDragEnd: (DragEndDetails details) { if (_controllerWasPlaying) { - _podCtr.videoCtr?.play(); + podCtr.videoCtr?.play(); } - _podCtr.toggleVideoOverlay(); + podCtr.toggleVideoOverlay(); if (widget.onDragEnd != null) { widget.onDragEnd?.call(); @@ -120,11 +119,11 @@ class _PodProgressBarState extends State { child: GetBuilder( tag: widget.tag, id: 'overlay', - builder: (_podCtr) => CustomPaint( + builder: (podCtr) => CustomPaint( painter: _ProgressBarPainter( videoPlayerValue!, podProgressBarConfig: widget.podProgressBarConfig.copyWith( - circleHandlerRadius: _podCtr.isOverlayVisible || + circleHandlerRadius: podCtr.isOverlayVisible || widget .podProgressBarConfig.alwaysVisibleCircleHandler ? widget.podProgressBarConfig.circleHandlerRadius diff --git a/pubspec.yaml b/pubspec.yaml index 42fdd23e..c04b2050 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,29 +1,28 @@ name: pod_player description: Vimeo and youtube player for flutter, Pod player provides customizable video player controls that support android, ios and web. -version: 0.1.0 +version: 0.2.2 homepage: https://github.com/newtaDev/pod_player environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=3.0.0 <4.0.0" flutter: ">=1.17.0" dependencies: flutter: sdk: flutter - # ui - lottie: ^1.3.0 # services - video_player: ^2.4.5 - http: ^0.13.4 - get: ^4.6.5 - wakelock: ^0.6.1+2 - universal_html: ^2.0.8 - youtube_explode_dart: ^1.11.0 + video_player: ^2.8.5 + http: ^1.2.1 + get: ^4.6.6 + wakelock_plus: ^1.2.4 + universal_html: ^2.2.4 + youtube_explode_dart: ^2.2.0 + dev_dependencies: flutter_test: sdk: flutter - very_good_analysis: ^2.4.0 + very_good_analysis: ^5.0.0+1 -flutter: - assets: - - assets/ +screenshots: + - description: Pod video player logo + path: screenshots/logo.png diff --git a/screenshots/logo.png b/screenshots/logo.png new file mode 100644 index 00000000..ab58ee62 Binary files /dev/null and b/screenshots/logo.png differ diff --git a/scripts/verify_pub.sh b/scripts/verify_pub.sh index eb21b5a3..8e78c89e 100644 --- a/scripts/verify_pub.sh +++ b/scripts/verify_pub.sh @@ -1,7 +1,7 @@ #!/bin/bash echo "--------------- Format code ---------------" -flutter format . +dart format . echo "--------------- --dry-run ---------------" dart pub publish --dry-run echo "--------------- verify pub score ---------------"