diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..38b24ad --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +# This file was copied from flutter/plugins/.clang-format. +BasedOnStyle: Google diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f60586b --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +# This file was copied from flutter/plugins/.gitignore. + +.DS_Store +.atom/ +.idea/ +.vscode/ + +.packages +.pub/ +.dart_tool/ +pubspec.lock +flutter_export_environment.sh + +examples/all_plugins/pubspec.yaml + +Podfile.lock +Pods/ +.symlinks/ +**/Flutter/App.framework/ +**/Flutter/ephemeral/ +**/Flutter/Flutter.podspec +**/Flutter/Flutter.framework/ +**/Flutter/Generated.xcconfig +**/Flutter/flutter_assets/ + +ServiceDefinitions.json +xcuserdata/ +**/DerivedData/ + +local.properties +keystore.properties +.gradle/ +gradlew +gradlew.bat +gradle-wrapper.jar +.flutter-plugins-dependencies +*.iml + +generated_plugin_registrant.dart +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m +generated_plugin_registrant.cc +GeneratedPluginRegistrant.java +GeneratedPluginRegistrant.swift +build/ +.flutter-plugins + +.project +.classpath +.settings + +# Downloaded by the plugin tools. +google-java-format-1.3-all-deps.jar diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..454d129 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2021 Sony Group Corporation. All rights reserved. +Copyright (c) 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the names of the copyright holders nor the names of the + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index f102289..bf9a6bf 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,12 @@ This repo is a companion repo to the [flutter-elinux](https://github.com/sony/flutter-elinux). It contains the source code for Flutter plugins for eLinux. Check the packages directory for all plugins. ## Plugins -Coming soon. +Basically, the plugins for elinux are designed to be API compatible with the the official Flutter plugins. + +| Package | Original package | +| -------- | ---------------- | +| [video_player_elinux](packages/video_player) | [video_player](https://github.com/flutter/plugins/tree/master/packages/video_player) (Flutter official) | + ## Companion repos | Repo | Purpose | diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..94efed4 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,252 @@ +# This file was copied from flutter/plugins/analysis_options.yaml. + +# This is a copy (as of March 2021) of flutter/flutter's analysis_options file, +# with minimal changes for this repository. The goal is to move toward using a +# shared set of analysis options as much as possible, and eventually a shared +# file. +# +# Plugins that have not yet switched from the previous set of options have a +# local analysis_options.yaml that points to analysis_options_legacy.yaml +# instead. + +# Specify analysis options. +# +# Until there are meta linter rules, each desired lint must be explicitly enabled. +# See: https://github.com/dart-lang/linter/issues/288 +# +# For a list of lints, see: http://dart-lang.github.io/linter/lints/ +# See the configuration guide for more +# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer +# +# There are other similar analysis options files in the flutter repos, +# which should be kept in sync with this file: +# +# - analysis_options.yaml (this file) +# - packages/flutter/lib/analysis_options_user.yaml +# - https://github.com/flutter/plugins/blob/master/analysis_options.yaml +# - https://github.com/flutter/engine/blob/master/analysis_options.yaml +# +# This file contains the analysis options used by Flutter tools, such as IntelliJ, +# Android Studio, and the `flutter analyze` command. + +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false + errors: + # treat missing required parameters as a warning (not a hint) + missing_required_param: warning + # treat missing returns as a warning (not a hint) + missing_return: warning + # allow having TODOs in the code + todo: ignore + # allow self-reference to deprecated members (we do this because otherwise we have + # to annotate every member in every test, assert, etc, when we deprecate something) + deprecated_member_use_from_same_package: ignore + # Ignore analyzer hints for updating pubspecs when using Future or + # Stream and not importing dart:async + # Please see https://github.com/flutter/flutter/pull/24528 for details. + sdk_version_async_exported_from_core: ignore + ### Local flutter/plugins changes ### + # Allow null checks for as long as mixed mode is officially supported. + unnecessary_null_comparison: false + always_require_non_null_named_parameters: false # not needed with nnbd + # TODO(https://github.com/flutter/flutter/issues/74381): + # Clean up existing unnecessary imports, and remove line to ignore. + unnecessary_import: ignore + exclude: + # Ignore generated files + - '**/*.g.dart' + - 'lib/src/generated/*.dart' + - '**/*.mocks.dart' # Mockito @GenerateMocks + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_declare_return_types + - always_put_control_body_on_new_line + # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 + - always_require_non_null_named_parameters + - always_specify_types + # - always_use_package_imports # we do this commonly + - annotate_overrides + # - avoid_annotating_with_dynamic # conflicts with always_specify_types + # - avoid_as # required for implicit-casts: true + - avoid_bool_literals_in_conditional_expressions + # - avoid_catches_without_on_clauses # we do this commonly + # - avoid_catching_errors # we do this commonly + - avoid_classes_with_only_static_members + # - avoid_double_and_int_checks # only useful when targeting JS runtime + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + # - avoid_escaping_inner_quotes # not yet tested + - avoid_field_initializers_in_const_classes + - avoid_function_literals_in_foreach_calls + # - avoid_implementing_value_types # not yet tested + - avoid_init_to_null + # - avoid_js_rounded_ints # only useful when targeting JS runtime + - avoid_null_checks_in_equality_operators + # - avoid_positional_boolean_parameters # not yet tested + # - avoid_print # not yet tested + # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) + # - avoid_redundant_argument_values # not yet tested + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + # - avoid_returning_null # there are plenty of valid reasons to return null + # - avoid_returning_null_for_future # not yet tested + - avoid_returning_null_for_void + # - avoid_returning_this # there are plenty of valid reasons to return this + # - avoid_setters_without_getters # not yet tested + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + # - avoid_type_to_string # we do this commonly + - avoid_types_as_parameter_names + # - avoid_types_on_closure_parameters # conflicts with always_specify_types + # - avoid_unnecessary_containers # not yet tested + - avoid_unused_constructor_parameters + - avoid_void_async + # - avoid_web_libraries_in_flutter # not yet tested + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + # - cascade_invocations # not yet tested + - cast_nullable_to_non_nullable + # - close_sinks # not reliable enough + # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 + # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 + - control_flow_in_finally + # - curly_braces_in_flow_control_structures # not required by flutter style + # - diagnostic_describe_all_properties # not yet tested + - directives_ordering + # - do_not_use_environment # we do this commonly + - empty_catches + - empty_constructor_bodies + - empty_statements + - exhaustive_cases + # - file_names # not yet tested + - flutter_style_todos + - hash_and_equals + - implementation_imports + # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 + - iterable_contains_unrelated_type + # - join_return_with_assignment # not required by flutter style + - leading_newlines_in_multiline_strings + - library_names + - library_prefixes + # - lines_longer_than_80_chars # not required by flutter style + - list_remove_unrelated_type + # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 + # - missing_whitespace_between_adjacent_strings # not yet tested + - no_adjacent_strings_in_list + # - no_default_cases # too many false positives + - no_duplicate_case_values + - no_logic_in_create_state + # - no_runtimeType_toString # ok in tests; we enable this only in packages/ + - non_constant_identifier_names + - null_check_on_nullable_type_parameter + # - null_closures # not required by flutter style + # - omit_local_variable_types # opposite of always_specify_types + # - one_member_abstracts # too many false positives + # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 + - overridden_fields + - package_api_docs + # - package_names # non conforming packages in sdk + - package_prefixed_library_names + # - parameter_assignments # we do this commonly + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + # - prefer_asserts_with_message # not required by flutter style + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + # - prefer_constructors_over_static_methods # far too many false positives + - prefer_contains + # - prefer_double_quotes # opposite of prefer_single_quotes + - prefer_equal_for_default_values + # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + # - prefer_function_declarations_over_variables # not yet tested + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + # - prefer_int_literals # not yet tested + # - prefer_interpolation_to_compose_strings # not yet tested + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + # - prefer_mixin # https://github.com/dart-lang/language/issues/32 + # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 + # - prefer_relative_imports # not yet tested + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + # - provide_deprecation_message # not yet tested + # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml + - recursive_getters + # - sized_box_for_whitespace # not yet tested + - slash_for_doc_comments + # - sort_child_properties_last # not yet tested + - sort_constructors_first + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - tighten_type_of_initializing_formals + # - type_annotate_public_apis # subset of always_specify_types + - type_init_formals + # - unawaited_futures # too many false positives + # - unnecessary_await_in_return # not yet tested + - unnecessary_brace_in_string_interps + - unnecessary_const + # - unnecessary_final # conflicts with prefer_final_locals + - unnecessary_getters_setters + # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 + - unnecessary_new + - unnecessary_null_aware_assignments + # - unnecessary_null_checks # not yet tested + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_overrides + - unnecessary_parenthesis + # - unnecessary_raw_strings # not yet tested + - unnecessary_statements + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - unrelated_type_equality_checks + # - unsafe_html # not yet tested + - use_full_hex_values_for_flutter_colors + # - use_function_type_syntax_for_parameters # not yet tested + - use_is_even_rather_than_modulo + # - use_key_in_widget_constructors # not yet tested + - use_late_for_private_fields_and_variables + - use_raw_strings + - use_rethrow_when_possible + # - use_setters_to_change_properties # not yet tested + # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 + # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review + - valid_regexps + - void_checks + ### Local flutter/plugins changes ### + # These are from flutter/flutter/packages, so will need to be preserved + # separately when moving to a shared file. + - no_runtimeType_toString # use objectRuntimeType from package:foundation + - public_member_api_docs # see https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#documentation-dartdocs-javadocs-etc + # Flutter has a specific use case for dependencies that are intentionally + # not sorted, which doesn't apply to this repo. + - sort_pub_dependencies diff --git a/packages/video_player/.gitignore b/packages/video_player/.gitignore new file mode 100644 index 0000000..e9dc58d --- /dev/null +++ b/packages/video_player/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md new file mode 100644 index 0000000..a1915cf --- /dev/null +++ b/packages/video_player/CHANGELOG.md @@ -0,0 +1,2 @@ +## 0.9.0 +* First release. diff --git a/packages/video_player/LICENSE b/packages/video_player/LICENSE new file mode 100644 index 0000000..454d129 --- /dev/null +++ b/packages/video_player/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2021 Sony Group Corporation. All rights reserved. +Copyright (c) 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the names of the copyright holders nor the names of the + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/packages/video_player/README.md b/packages/video_player/README.md new file mode 100644 index 0000000..3fb787c --- /dev/null +++ b/packages/video_player/README.md @@ -0,0 +1,33 @@ +# video_player_elinux + +The implementation of the Video Player plugin for flutter elinux. APIs are designed to be API compatible with the the official [`video_player`](https://github.com/flutter/plugins/tree/master/packages/video_player). + +![image](https://user-images.githubusercontent.com/62131389/124210378-43f06400-db26-11eb-8723-40dad0eb67b0.png) + +## Required libraries + +This plugin uses [GStreamer](https://gstreamer.freedesktop.org/) internally. + +```Shell +$ sudo apt install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer1.0-0 \ + gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ + gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav +``` + +## Getting Started + +### `pubspec.yaml` +```yaml +dependencies: + video_player: ^2.1.12 + video_player_elinux: + git: + url: https://github.com/sony/flutter-elinux-plugins/tree/main/packages/video_player/video_player + ref: ^0.9.0 +``` + +### Source code +Import `video_player` in your Dart code: +```dart +import 'package:video_player/video_player.dart'; +``` diff --git a/packages/video_player/elinux/.gitignore b/packages/video_player/elinux/.gitignore new file mode 100644 index 0000000..f68e1be --- /dev/null +++ b/packages/video_player/elinux/.gitignore @@ -0,0 +1 @@ +flutter/ diff --git a/packages/video_player/elinux/CMakeLists.txt b/packages/video_player/elinux/CMakeLists.txt new file mode 100644 index 0000000..4d388b7 --- /dev/null +++ b/packages/video_player/elinux/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.10) +set(PROJECT_NAME "video_player_elinux") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "video_player_elinux_plugin") + +find_package(PkgConfig) +pkg_check_modules(GLIB REQUIRED glib-2.0) +pkg_check_modules(GSTREAMER REQUIRED + gstreamer-1.0 + gstreamer-app-1.0 + gstreamer-video-1.0 + gstreamer-audio-1.0 +) + +add_library(${PLUGIN_NAME} SHARED + "video_player_elinux_plugin.cc" + "gst_video_player.cc" +) +apply_standard_settings(${PLUGIN_NAME}) +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) + +target_include_directories(${PLUGIN_NAME} + PRIVATE + ${GLIB_INCLUDE_DIRS} + ${GSTREAMER_INCLUDE_DIRS} +) + +target_link_libraries(${PLUGIN_NAME} + PRIVATE + ${GLIB_LIBRARIES} + ${GSTREAMER_LIBRARIES} +) + +# List of absolute paths to libraries that should be bundled with the plugin +set(video_player_elinux_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/packages/video_player/elinux/gst_video_player.cc b/packages/video_player/elinux/gst_video_player.cc new file mode 100644 index 0000000..40a3fc2 --- /dev/null +++ b/packages/video_player/elinux/gst_video_player.cc @@ -0,0 +1,395 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gst_video_player.h" + +#include +#include +#include + +#include + +GstVideoPlayer::GstVideoPlayer( + const std::string& uri, std::unique_ptr handler) + : stream_handler_(std::move(handler)) { + gst_.pipeline = nullptr; + gst_.playbin = nullptr; + gst_.video_convert = nullptr; + gst_.video_sink = nullptr; + gst_.output = nullptr; + gst_.bus = nullptr; + gst_.buffer = nullptr; + + uri_ = ParseUri(uri); + if (!CreatePipeline()) { + std::cerr << "Failed to create a pipeline" << std::endl; + DestroyPipeline(); + return; + } + + // Prerolls before getting information from the pipeline. + Preroll(); + + // Sets internal video size and buffier. + GetVideoSize(width_, height_); + pixels_.reset(new uint32_t[width_ * height_]); + + stream_handler_->OnNotifyInitialized(); +} + +GstVideoPlayer::~GstVideoPlayer() { + Stop(); + DestroyPipeline(); +} + +// static +void GstVideoPlayer::GstLibraryLoad() { gst_init(NULL, NULL); } + +// static +void GstVideoPlayer::GstLibraryUnload() { gst_deinit(); } + +bool GstVideoPlayer::Play() { + if (gst_element_set_state(gst_.pipeline, GST_STATE_PLAYING) == + GST_STATE_CHANGE_FAILURE) { + std::cerr << "Failed to change the state to PLAYING" << std::endl; + return false; + } + return true; +} + +bool GstVideoPlayer::Pause() { + if (gst_element_set_state(gst_.pipeline, GST_STATE_PAUSED) == + GST_STATE_CHANGE_FAILURE) { + std::cerr << "Failed to change the state to PAUSED" << std::endl; + return false; + } + return true; +} + +bool GstVideoPlayer::Stop() { + if (gst_element_set_state(gst_.pipeline, GST_STATE_READY) == + GST_STATE_CHANGE_FAILURE) { + std::cerr << "Failed to change the state to READY" << std::endl; + return false; + } + return true; +} + +bool GstVideoPlayer::SetVolume(double volume) { + if (!gst_.playbin) { + return false; + } + + volume_ = volume; + g_object_set(gst_.playbin, "volume", volume, NULL); + return true; +} + +bool GstVideoPlayer::SetPlaybackRate(double rate) { + if (!gst_.playbin) { + return false; + } + + if (rate <= 0) { + std::cerr << "Rate " << rate << " is not supported" << std::endl; + return false; + } + + if (!gst_element_seek(gst_.pipeline, rate, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, + GetCurrentPosition() * GST_MSECOND, GST_SEEK_TYPE_SET, + GST_CLOCK_TIME_NONE)) { + std::cerr << "Failed to set playback rate to " << rate + << " (gst_element_seek failed)" << std::endl; + return false; + } + + playback_rate_ = rate; + mute_ = (rate < 0.5 || rate > 2); + g_object_set(gst_.playbin, "mute", mute_, NULL); + + return true; +} + +bool GstVideoPlayer::SetSeek(int64_t position) { + auto nanosecond = position * 1000 * 1000; + if (!gst_element_seek( + gst_.pipeline, playback_rate_, GST_FORMAT_TIME, + (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), + GST_SEEK_TYPE_SET, nanosecond, GST_SEEK_TYPE_SET, + GST_CLOCK_TIME_NONE)) { + std::cerr << "Failed to seek " << nanosecond << std::endl; + return false; + } + return true; +} + +int64_t GstVideoPlayer::GetDuration() { + GstFormat fmt = GST_FORMAT_TIME; + int64_t duration_msec; + if (!gst_element_query_duration(gst_.pipeline, fmt, &duration_msec)) { + std::cerr << "Failed to get duration" << std::endl; + return -1; + } + duration_msec /= GST_MSECOND; + return duration_msec; +} + +int64_t GstVideoPlayer::GetCurrentPosition() { + gint64 position = 0; + if (!gst_element_query_position(gst_.pipeline, GST_FORMAT_TIME, &position)) { + std::cerr << "Failed to get current position" << std::endl; + } + return position / GST_MSECOND; +} + +const uint8_t* GstVideoPlayer::GetFrameBuffer() { + std::shared_lock lock(mutex_buffer_); + if (!gst_.buffer) { + return nullptr; + } + + const uint32_t pixel_bytes = width_ * height_ * 4; + gst_buffer_extract(gst_.buffer, 0, pixels_.get(), pixel_bytes); + return reinterpret_cast(pixels_.get()); +} + +// Creats a video pipeline using playbin. +// $ playbin uri= video-sink="videoconvert ! video/x-raw,format=RGBA ! +// fakesink" +bool GstVideoPlayer::CreatePipeline() { + gst_.pipeline = gst_pipeline_new("pipeline"); + if (!gst_.pipeline) { + std::cerr << "Failed to create a pipeline" << std::endl; + return false; + } + gst_.playbin = gst_element_factory_make("playbin", "playbin"); + if (!gst_.playbin) { + std::cerr << "Failed to create a source" << std::endl; + return false; + } + gst_.video_convert = gst_element_factory_make("videoconvert", "videoconvert"); + if (!gst_.video_convert) { + std::cerr << "Failed to create a videoconvert" << std::endl; + return false; + } + gst_.video_sink = gst_element_factory_make("fakesink", "videosink"); + if (!gst_.video_sink) { + std::cerr << "Failed to create a videosink" << std::endl; + return false; + } + gst_.output = gst_bin_new("output"); + if (!gst_.output) { + std::cerr << "Failed to create an output" << std::endl; + return false; + } + gst_.bus = gst_pipeline_get_bus(GST_PIPELINE(gst_.pipeline)); + if (!gst_.bus) { + std::cerr << "Failed to create a bus" << std::endl; + return false; + } + gst_bus_set_sync_handler(gst_.bus, (GstBusSyncHandler)HandleGstMessage, this, + NULL); + + // Sets properties to fakesink to get the callback of a decoded frame. + g_object_set(G_OBJECT(gst_.video_sink), "sync", TRUE, "qos", FALSE, NULL); + g_object_set(G_OBJECT(gst_.video_sink), "signal-handoffs", TRUE, NULL); + g_signal_connect(G_OBJECT(gst_.video_sink), "handoff", + G_CALLBACK(HandoffHandler), this); + gst_bin_add_many(GST_BIN(gst_.output), gst_.video_convert, gst_.video_sink, + NULL); + + // Adds caps to the converter to convert the color format to RGBA. + auto* caps = gst_caps_from_string("video/x-raw,format=RGBA"); + auto link_ok = + gst_element_link_filtered(gst_.video_convert, gst_.video_sink, caps); + gst_caps_unref(caps); + if (!link_ok) { + std::cerr << "Failed to link elements" << std::endl; + return false; + } + + auto* sinkpad = gst_element_get_static_pad(gst_.video_convert, "sink"); + auto* ghost_sinkpad = gst_ghost_pad_new("sink", sinkpad); + gst_pad_set_active(ghost_sinkpad, TRUE); + gst_element_add_pad(gst_.output, ghost_sinkpad); + + // Sets properties to playbin. + g_object_set(gst_.playbin, "uri", uri_.c_str(), NULL); + g_object_set(gst_.playbin, "video-sink", gst_.output, NULL); + gst_bin_add_many(GST_BIN(gst_.pipeline), gst_.playbin, NULL); + + return true; +} + +void GstVideoPlayer::Preroll() { + if (!gst_.playbin) { + return; + } + + auto result = gst_element_set_state(gst_.pipeline, GST_STATE_PAUSED); + if (result == GST_STATE_CHANGE_FAILURE) { + std::cerr << "Failed to change the state to PAUSED" << std::endl; + return; + } + + // Waits until the state becomes GST_STATE_PAUSED. + if (result == GST_STATE_CHANGE_ASYNC) { + GstState state; + result = + gst_element_get_state(gst_.pipeline, &state, NULL, GST_CLOCK_TIME_NONE); + if (result == GST_STATE_CHANGE_FAILURE) { + std::cerr << "Failed to get the current state" << std::endl; + } + } +} + +void GstVideoPlayer::DestroyPipeline() { + if (gst_.video_sink) { + g_object_set(G_OBJECT(gst_.video_sink), "signal-handoffs", FALSE, NULL); + } + + if (gst_.pipeline) { + gst_element_set_state(gst_.pipeline, GST_STATE_NULL); + } + + if (gst_.buffer) { + gst_buffer_unref(gst_.buffer); + gst_.buffer = nullptr; + } + + if (gst_.bus) { + gst_object_unref(gst_.bus); + gst_.bus = nullptr; + } + + if (gst_.pipeline) { + gst_object_unref(gst_.pipeline); + gst_.pipeline = nullptr; + } + + if (gst_.playbin) { + gst_.playbin = nullptr; + } + + if (gst_.output) { + gst_.output = nullptr; + } + + if (gst_.video_sink) { + gst_.video_sink = nullptr; + } + + if (gst_.video_convert) { + gst_.video_convert = nullptr; + } +} + +std::string GstVideoPlayer::ParseUri(const std::string& uri) { + if (gst_uri_is_valid(uri.c_str())) { + return uri; + } + + const auto* filename_uri = gst_filename_to_uri(uri.c_str(), NULL); + if (!filename_uri) { + std::cerr << "Faild to open " << uri.c_str() << std::endl; + return uri; + } + std::string result_uri(filename_uri); + delete filename_uri; + + return result_uri; +} + +void GstVideoPlayer::GetVideoSize(int32_t& width, int32_t& height) { + if (!gst_.pipeline || !gst_.video_sink) { + std::cerr + << "Failed to get video size. The pileline hasn't initialized yet."; + return; + } + + auto* sink_pad = gst_element_get_static_pad(gst_.video_sink, "sink"); + if (!sink_pad) { + std::cerr << "Failed to get a pad"; + return; + } + + auto* caps = gst_pad_get_current_caps(sink_pad); + auto* structure = gst_caps_get_structure(caps, 0); + if (!structure) { + std::cerr << "Failed to get a structure"; + return; + } + + gst_structure_get_int(structure, "width", &width); + gst_structure_get_int(structure, "height", &height); +} + +// static +void GstVideoPlayer::HandoffHandler(GstElement* fakesink, GstBuffer* buf, + GstPad* new_pad, gpointer user_data) { + auto* self = reinterpret_cast(user_data); + auto* caps = gst_pad_get_current_caps(new_pad); + auto* structure = gst_caps_get_structure(caps, 0); + + int width; + int height; + gst_structure_get_int(structure, "width", &width); + gst_structure_get_int(structure, "height", &height); + if (width != self->width_ || height != self->height_) { + self->width_ = width; + self->height_ = height; + self->pixels_.reset(new uint32_t[width * height]); + std::cout << "Pixel buffer size: width = " << width + << ", height = " << height << std::endl; + } + + std::lock_guard lock(self->mutex_buffer_); + if (self->gst_.buffer) { + gst_buffer_unref(self->gst_.buffer); + self->gst_.buffer = nullptr; + } + self->gst_.buffer = gst_buffer_ref(buf); + self->stream_handler_->OnNotifyFrameDecoded(); +} + +// static +gboolean GstVideoPlayer::HandleGstMessage(GstBus* bus, GstMessage* message, + gpointer user_data) { + switch (GST_MESSAGE_TYPE(message)) { + case GST_MESSAGE_EOS: { + auto* self = reinterpret_cast(user_data); + self->stream_handler_->OnNotifyCompleted(); + if (self->auto_repeat_) { + self->SetSeek(0); + } + break; + } + case GST_MESSAGE_WARNING: { + gchar* debug; + GError* error; + gst_message_parse_warning(message, &error, &debug); + g_printerr("WARNING from element %s: %s\n", GST_OBJECT_NAME(message->src), + error->message); + g_printerr("Warning details: %s\n", debug); + g_free(debug); + g_error_free(error); + break; + } + case GST_MESSAGE_ERROR: { + gchar* debug; + GError* error; + gst_message_parse_error(message, &error, &debug); + g_printerr("ERROR from element %s: %s\n", GST_OBJECT_NAME(message->src), + error->message); + g_printerr("Error details: %s\n", debug); + g_free(debug); + g_error_free(error); + break; + } + default: + break; + } + return TRUE; +} diff --git a/packages/video_player/elinux/gst_video_player.h b/packages/video_player/elinux/gst_video_player.h new file mode 100644 index 0000000..eb58907 --- /dev/null +++ b/packages/video_player/elinux/gst_video_player.h @@ -0,0 +1,72 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_GST_VIDEO_PLAYER_H_ +#define PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_GST_VIDEO_PLAYER_H_ + +#include + +#include +#include +#include + +#include "video_player_stream_handler.h" + +class GstVideoPlayer { + public: + GstVideoPlayer(const std::string& uri, + std::unique_ptr handler); + ~GstVideoPlayer(); + + static void GstLibraryLoad(); + static void GstLibraryUnload(); + + bool Play(); + bool Pause(); + bool Stop(); + bool SetVolume(double volume); + bool SetPlaybackRate(double rate); + void SetAutoRepeat(bool auto_repeat) { auto_repeat_ = auto_repeat; }; + bool SetSeek(int64_t position); + int64_t GetDuration(); + int64_t GetCurrentPosition(); + const uint8_t* GetFrameBuffer(); + int32_t GetWidth() const { return width_; }; + int32_t GetHeight() const { return height_; }; + + private: + struct GstVideoElements { + GstElement* pipeline; + GstElement* playbin; + GstElement* video_convert; + GstElement* video_sink; + GstElement* output; + GstBus* bus; + GstBuffer* buffer; + }; + + static void HandoffHandler(GstElement* fakesink, GstBuffer* buf, + GstPad* new_pad, gpointer user_data); + static gboolean HandleGstMessage(GstBus* bus, GstMessage* message, + gpointer user_data); + std::string ParseUri(const std::string& uri); + bool CreatePipeline(); + void DestroyPipeline(); + void Preroll(); + void GetVideoSize(int32_t& width, int32_t& height); + + GstVideoElements gst_; + std::string uri_; + std::unique_ptr pixels_; + int32_t width_; + int32_t height_; + double volume_ = 1.0; + double playback_rate_ = 1.0; + bool mute_ = false; + bool auto_repeat_ = false; + std::shared_mutex mutex_buffer_; + std::unique_ptr stream_handler_; +}; + +#endif // PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_GST_VIDEO_PLAYER_H_ diff --git a/packages/video_player/elinux/include/video_player_elinux/video_player_elinux_plugin.h b/packages/video_player/elinux/include/video_player_elinux/video_player_elinux_plugin.h new file mode 100644 index 0000000..2722d98 --- /dev/null +++ b/packages/video_player/elinux/include/video_player_elinux/video_player_elinux_plugin.h @@ -0,0 +1,23 @@ +#ifndef FLUTTER_PLUGIN_VIDEO_PLAYER_ELINUX_PLUGIN_H_ +#define FLUTTER_PLUGIN_VIDEO_PLAYER_ELINUX_PLUGIN_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_PLUGIN_EXPORT void VideoPlayerElinuxPluginRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_PLUGIN_VIDEO_PLAYER_ELINUX_PLUGIN_H_ diff --git a/packages/video_player/elinux/messages/create_message.h b/packages/video_player/elinux/messages/create_message.h new file mode 100644 index 0000000..fc0fd0f --- /dev/null +++ b/packages/video_player/elinux/messages/create_message.h @@ -0,0 +1,90 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_CREATE_MESSAGE_H_ +#define PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_CREATE_MESSAGE_H_ + +#include +#include + +class CreateMessage { + public: + CreateMessage() = default; + ~CreateMessage() = default; + + // Prevent copying. + CreateMessage(CreateMessage const&) = default; + CreateMessage& operator=(CreateMessage const&) = default; + + void SetAsset(const std::string& asset) { asset_ = asset; } + + std::string GetAsset() const { return asset_; } + + void SetUri(const std::string& uri) { uri_ = uri; } + + std::string GetUri() const { return uri_; } + + void SetPackageName(const std::string& packageName) { + package_name_ = packageName; + } + + std::string GetPackageName() const { return package_name_; } + + void SetFormatHint(const std::string& formatHint) { + format_hint_ = formatHint; + } + + std::string GetFormatHint() const { return format_hint_; } + + flutter::EncodableValue ToMap() { + // todo: Add httpHeaders. + flutter::EncodableMap map = { + {flutter::EncodableValue("asset"), flutter::EncodableValue(asset_)}, + {flutter::EncodableValue("uri"), flutter::EncodableValue(uri_)}, + {flutter::EncodableValue("packageName"), + flutter::EncodableValue(package_name_)}, + {flutter::EncodableValue("formatHint"), + flutter::EncodableValue(format_hint_)}}; + return flutter::EncodableValue(map); + } + + static CreateMessage FromMap(const flutter::EncodableValue& value) { + CreateMessage message; + if (std::holds_alternative(value)) { + auto map = std::get(value); + + flutter::EncodableValue& asset = map[flutter::EncodableValue("asset")]; + if (std::holds_alternative(asset)) { + message.SetAsset(std::get(asset)); + } + + flutter::EncodableValue& uri = map[flutter::EncodableValue("uri")]; + if (std::holds_alternative(uri)) { + message.SetUri(std::get(uri)); + } + + flutter::EncodableValue& packageName = + map[flutter::EncodableValue("packageName")]; + if (std::holds_alternative(packageName)) { + message.SetPackageName(std::get(uri)); + } + + flutter::EncodableValue& formatHint = + map[flutter::EncodableValue("formatHint")]; + if (std::holds_alternative(formatHint)) { + message.SetFormatHint(std::get(formatHint)); + } + } + + return message; + } + + private: + std::string asset_; + std::string uri_; + std::string package_name_; + std::string format_hint_; +}; + +#endif // PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_CREATE_MESSAGE_H_ diff --git a/packages/video_player/elinux/messages/looping_message.h b/packages/video_player/elinux/messages/looping_message.h new file mode 100644 index 0000000..2219f37 --- /dev/null +++ b/packages/video_player/elinux/messages/looping_message.h @@ -0,0 +1,63 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_LOOPING_MESSAGE_H_ +#define PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_LOOPING_MESSAGE_H_ + +#include +#include + +class LoopingMessage { + public: + LoopingMessage() = default; + ~LoopingMessage() = default; + + // Prevent copying. + LoopingMessage(LoopingMessage const&) = default; + LoopingMessage& operator=(LoopingMessage const&) = default; + + void SetTextureId(int64_t texture_id) { texture_id_ = texture_id; } + + int64_t GetTextureId() const { return texture_id_; } + + void SetIsLooping(bool is_looping) { is_looping_ = is_looping; } + + bool GetIsLooping() const { return is_looping_; } + + flutter::EncodableValue ToMap() { + flutter::EncodableMap map = {{flutter::EncodableValue("textureId"), + flutter::EncodableValue(texture_id_)}, + {flutter::EncodableValue("isLooping"), + flutter::EncodableValue(is_looping_)}}; + return flutter::EncodableValue(map); + } + + static LoopingMessage FromMap(const flutter::EncodableValue& value) { + LoopingMessage message; + if (std::holds_alternative(value)) { + auto map = std::get(value); + + flutter::EncodableValue& texture_id = + map[flutter::EncodableValue("textureId")]; + if (std::holds_alternative(texture_id) || + std::holds_alternative(texture_id)) { + message.SetTextureId(texture_id.LongValue()); + } + + flutter::EncodableValue& is_looping = + map[flutter::EncodableValue("isLooping")]; + if (std::holds_alternative(is_looping)) { + message.SetIsLooping(std::get(is_looping)); + } + } + + return message; + } + + private: + int64_t texture_id_ = 0; + bool is_looping_ = false; +}; + +#endif // PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_LOOPING_MESSAGE_H_ diff --git a/packages/video_player/elinux/messages/messages.h b/packages/video_player/elinux/messages/messages.h new file mode 100644 index 0000000..4f642e0 --- /dev/null +++ b/packages/video_player/elinux/messages/messages.h @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_MESSAGES_H_ +#define PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_MESSAGES_H_ + +#include "create_message.h" +#include "looping_message.h" +#include "mix_with_others_message.h" +#include "playback_speed_message.h" +#include "position_message.h" +#include "texture_message.h" +#include "volume_message.h" + +#endif // PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_MESSAGES_H_ diff --git a/packages/video_player/elinux/messages/mix_with_others_message.h b/packages/video_player/elinux/messages/mix_with_others_message.h new file mode 100644 index 0000000..78798a5 --- /dev/null +++ b/packages/video_player/elinux/messages/mix_with_others_message.h @@ -0,0 +1,52 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_MIX_WITH_OTHERS_MESSAGE_H_ +#define PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_MIX_WITH_OTHERS_MESSAGE_H_ + +#include +#include + +class MixWithOthersMessage { + public: + MixWithOthersMessage() = default; + ~MixWithOthersMessage() = default; + + // Prevent copying. + MixWithOthersMessage(MixWithOthersMessage const&) = default; + MixWithOthersMessage& operator=(MixWithOthersMessage const&) = default; + + void SetMixWithOthers(bool mixWithOthers) { + mix_with_others_ = mixWithOthers; + } + + bool GetMixWithOthers() const { return mix_with_others_; } + + flutter::EncodableValue ToMap() { + flutter::EncodableMap map = {{flutter::EncodableValue("mixWithOthers"), + flutter::EncodableValue(mix_with_others_)}}; + + return flutter::EncodableValue(map); + } + + static MixWithOthersMessage FromMap(const flutter::EncodableValue& value) { + MixWithOthersMessage message; + if (std::holds_alternative(value)) { + auto map = std::get(value); + + flutter::EncodableValue& mixWithOthers = + map[flutter::EncodableValue("mixWithOthers")]; + if (std::holds_alternative(mixWithOthers)) { + message.SetMixWithOthers(std::get(mixWithOthers)); + } + } + + return message; + } + + private: + bool mix_with_others_ = false; +}; + +#endif // PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_MIX_WITH_OTHERS_MESSAGE_H_ diff --git a/packages/video_player/elinux/messages/playback_speed_message.h b/packages/video_player/elinux/messages/playback_speed_message.h new file mode 100644 index 0000000..147da22 --- /dev/null +++ b/packages/video_player/elinux/messages/playback_speed_message.h @@ -0,0 +1,62 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_PLAYBACK_SPEED_MESSAGE_H_ +#define PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_PLAYBACK_SPEED_MESSAGE_H_ + +#include +#include + +class PlaybackSpeedMessage { + public: + PlaybackSpeedMessage() = default; + ~PlaybackSpeedMessage() = default; + + // Prevent copying. + PlaybackSpeedMessage(PlaybackSpeedMessage const&) = default; + PlaybackSpeedMessage& operator=(PlaybackSpeedMessage const&) = default; + + void SetTextureId(int64_t texture_id) { texture_id_ = texture_id; } + + int64_t GetTextureId() const { return texture_id_; } + + void SetSpeed(double speed) { speed_ = speed; } + + double GetSpeed() const { return speed_; } + + flutter::EncodableValue ToMap() { + flutter::EncodableMap map = { + {flutter::EncodableValue("textureId"), + flutter::EncodableValue(texture_id_)}, + {flutter::EncodableValue("speed"), flutter::EncodableValue(speed_)}}; + return flutter::EncodableValue(map); + } + + static PlaybackSpeedMessage FromMap(const flutter::EncodableValue& value) { + PlaybackSpeedMessage message; + if (std::holds_alternative(value)) { + auto map = std::get(value); + + flutter::EncodableValue& texture_id = + map[flutter::EncodableValue("textureId")]; + if (std::holds_alternative(texture_id) || + std::holds_alternative(texture_id)) { + message.SetTextureId(texture_id.LongValue()); + } + + flutter::EncodableValue& speed = map[flutter::EncodableValue("speed")]; + if (std::holds_alternative(speed)) { + message.SetSpeed(std::get(speed)); + } + } + + return message; + } + + private: + int64_t texture_id_ = 0; + double speed_ = 1.0; +}; + +#endif // PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_PLAYBACK_SPEED_MESSAGE_H_ diff --git a/packages/video_player/elinux/messages/position_message.h b/packages/video_player/elinux/messages/position_message.h new file mode 100644 index 0000000..b1ce2f5 --- /dev/null +++ b/packages/video_player/elinux/messages/position_message.h @@ -0,0 +1,65 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_POSITION_MESSAGE_H_ +#define PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_POSITION_MESSAGE_H_ + +#include +#include + +class PositionMessage { + public: + PositionMessage() = default; + ~PositionMessage() = default; + + // Prevent copying. + PositionMessage(PositionMessage const&) = default; + PositionMessage& operator=(PositionMessage const&) = default; + + void SetTextureId(int64_t texture_id) { texture_id_ = texture_id; } + + int64_t GetTextureId() const { return texture_id_; } + + void SetPosition(int64_t position) { position_ = position; } + + int64_t GetPosition() const { return position_; } + + flutter::EncodableValue ToMap() { + flutter::EncodableMap toMapResult = {{flutter::EncodableValue("textureId"), + flutter::EncodableValue(texture_id_)}, + {flutter::EncodableValue("position"), + flutter::EncodableValue(position_)}}; + + return flutter::EncodableValue(toMapResult); + } + + static PositionMessage FromMap(const flutter::EncodableValue& value) { + PositionMessage message; + if (std::holds_alternative(value)) { + auto map = std::get(value); + + flutter::EncodableValue& texture_id = + map[flutter::EncodableValue("textureId")]; + if (std::holds_alternative(texture_id) || + std::holds_alternative(texture_id)) { + message.SetTextureId(texture_id.LongValue()); + } + + flutter::EncodableValue& position = + map[flutter::EncodableValue("position")]; + if (std::holds_alternative(position) || + std::holds_alternative(position)) { + message.SetPosition(position.LongValue()); + } + } + + return message; + } + + private: + int64_t texture_id_ = 0; + int64_t position_ = 0; +}; + +#endif // PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_POSITION_MESSAGE_H_ diff --git a/packages/video_player/elinux/messages/texture_message.h b/packages/video_player/elinux/messages/texture_message.h new file mode 100644 index 0000000..8b73018 --- /dev/null +++ b/packages/video_player/elinux/messages/texture_message.h @@ -0,0 +1,49 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_TEXTURE_MESSAGE_H_ +#define PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_TEXTURE_MESSAGE_H_ + +#include +#include + +class TextureMessage { + public: + TextureMessage() = default; + ~TextureMessage() = default; + + // Prevent copying. + TextureMessage(TextureMessage const&) = default; + TextureMessage& operator=(TextureMessage const&) = default; + + void SetTextureId(int64_t texture_id) { texture_id_ = texture_id; } + + int64_t GetTextureId() const { return texture_id_; } + + flutter::EncodableValue ToMap() { + flutter::EncodableMap map = {{flutter::EncodableValue("textureId"), + flutter::EncodableValue(texture_id_)}}; + return flutter::EncodableValue(map); + } + + static TextureMessage FromMap(const flutter::EncodableValue& value) { + TextureMessage message; + if (std::holds_alternative(value)) { + auto map = std::get(value); + + flutter::EncodableValue& texture_id = + map[flutter::EncodableValue("textureId")]; + if (std::holds_alternative(texture_id) || + std::holds_alternative(texture_id)) { + message.SetTextureId(texture_id.LongValue()); + } + } + return message; + } + + private: + int64_t texture_id_ = 0; +}; + +#endif // PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_TEXTURE_MESSAGE_H_ diff --git a/packages/video_player/elinux/messages/volume_message.h b/packages/video_player/elinux/messages/volume_message.h new file mode 100644 index 0000000..dd8d157 --- /dev/null +++ b/packages/video_player/elinux/messages/volume_message.h @@ -0,0 +1,62 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_VOLUME_MESSAGE_H_ +#define PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_VOLUME_MESSAGE_H_ + +#include +#include + +class VolumeMessage { + public: + VolumeMessage() = default; + ~VolumeMessage() = default; + + // Prevent copying. + VolumeMessage(VolumeMessage const&) = default; + VolumeMessage& operator=(VolumeMessage const&) = default; + + void SetTextureId(int64_t texture_id) { texture_id_ = texture_id; } + + int64_t GetTextureId() const { return texture_id_; } + + void SetVolume(double volume) { volume_ = volume; } + + double GetVolume() const { return volume_; } + + flutter::EncodableValue ToMap() { + flutter::EncodableMap map = { + {flutter::EncodableValue("textureId"), + flutter::EncodableValue(texture_id_)}, + {flutter::EncodableValue("volume"), flutter::EncodableValue(volume_)}}; + return flutter::EncodableValue(map); + } + + static VolumeMessage FromMap(const flutter::EncodableValue& value) { + VolumeMessage message; + if (std::holds_alternative(value)) { + auto map = std::get(value); + + flutter::EncodableValue& texture_id = + map[flutter::EncodableValue("textureId")]; + if (std::holds_alternative(texture_id) || + std::holds_alternative(texture_id)) { + message.SetTextureId(texture_id.LongValue()); + } + + flutter::EncodableValue& volume = map[flutter::EncodableValue("volume")]; + if (std::holds_alternative(volume)) { + message.SetVolume(std::get(volume)); + } + } + + return message; + } + + private: + int64_t texture_id_ = 0; + double volume_ = 0; +}; + +#endif // PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_MESSAGES_VOLUME_MESSAGE_H_ diff --git a/packages/video_player/elinux/video_player_elinux_plugin.cc b/packages/video_player/elinux/video_player_elinux_plugin.cc new file mode 100644 index 0000000..df8fc39 --- /dev/null +++ b/packages/video_player/elinux/video_player_elinux_plugin.cc @@ -0,0 +1,590 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "include/video_player_elinux/video_player_elinux_plugin.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gst_video_player.h" +#include "messages/messages.h" +#include "video_player_stream_handler_impl.h" + +namespace { +constexpr char kVideoPlayerApiChannelInitializeName[] = + "dev.flutter.pigeon.VideoPlayerApi.initialize"; +constexpr char kVideoPlayerApiChannelSetMixWithOthersName[] = + "dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers"; +constexpr char kVideoPlayerApiChannelCreateName[] = + "dev.flutter.pigeon.VideoPlayerApi.create"; +constexpr char kVideoPlayerApiChannelDisposeName[] = + "dev.flutter.pigeon.VideoPlayerApi.dispose"; +constexpr char kVideoPlayerApiChannelSetLoopingName[] = + "dev.flutter.pigeon.VideoPlayerApi.setLooping"; +constexpr char kVideoPlayerApiChannelSetVolumeName[] = + "dev.flutter.pigeon.VideoPlayerApi.setVolume"; +constexpr char kVideoPlayerApiChannelPauseName[] = + "dev.flutter.pigeon.VideoPlayerApi.pause"; +constexpr char kVideoPlayerApiChannelPlayName[] = + "dev.flutter.pigeon.VideoPlayerApi.play"; +constexpr char kVideoPlayerApiChannelPositionName[] = + "dev.flutter.pigeon.VideoPlayerApi.position"; +constexpr char kVideoPlayerApiChannelSetPlaybackSpeedName[] = + "dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed"; +constexpr char kVideoPlayerApiChannelSeekToName[] = + "dev.flutter.pigeon.VideoPlayerApi.seekTo"; + +constexpr char kVideoPlayerVideoEventsChannelName[] = + "flutter.io/videoPlayer/videoEvents"; + +constexpr char kEncodableMapkeyResult[] = "result"; +constexpr char kEncodableMapkeyError[] = "error"; + +class VideoPlayerPlugin : public flutter::Plugin { + public: + static void RegisterWithRegistrar(flutter::PluginRegistrar* registrar); + + VideoPlayerPlugin(flutter::PluginRegistrar* plugin_registrar, + flutter::TextureRegistrar* texture_registrar) + : plugin_registrar_(plugin_registrar), + texture_registrar_(texture_registrar) { + // Needs to call 'gst_init' that initializing the GStreamer library before + // using it. + GstVideoPlayer::GstLibraryLoad(); + } + virtual ~VideoPlayerPlugin() { + for (auto itr = players_.begin(); itr != players_.end(); itr++) { + auto texture_id = itr->first; + auto* player = itr->second.get(); + player->event_sink = nullptr; + if (player->event_channel) { + player->event_channel->SetStreamHandler(nullptr); + } + player->player = nullptr; + player->buffer = nullptr; + player->texture = nullptr; + texture_registrar_->UnregisterTexture(texture_id); + } + players_.clear(); + + GstVideoPlayer::GstLibraryUnload(); + } + + private: + struct FlutterVideoPlayer { + int64_t texture_id; + std::unique_ptr player; + std::unique_ptr texture; + std::unique_ptr buffer; + std::unique_ptr> + event_channel; + std::unique_ptr> event_sink; + }; + + void HandleInitializeMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply); + void HandleCreateMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply); + void HandleDisposeMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply); + void HandlePauseMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply); + void HandlePlayMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply); + void HandleSetLoopingMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply); + void HandleSetVolumeMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply); + void HandleSetMixWithOthersMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply); + void HandleSetPlaybackSpeedMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply); + void HandleSeekToMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply); + void HandlePositionMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply); + + void SendInitializedEventMessage(int64_t texture_id); + void SendPlayCompletedEventMessage(int64_t texture_id); + + flutter::EncodableValue WrapError(const std::string& message, + const std::string& code = std::string(), + const std::string& details = std::string()); + + flutter::PluginRegistrar* plugin_registrar_; + flutter::TextureRegistrar* texture_registrar_; + std::unordered_map> players_; +}; + +// static +void VideoPlayerPlugin::RegisterWithRegistrar( + flutter::PluginRegistrar* registrar) { + auto plugin = std::make_unique( + registrar, registrar->texture_registrar()); + + { + auto channel = + std::make_unique>( + registrar->messenger(), kVideoPlayerApiChannelInitializeName, + &flutter::StandardMessageCodec::GetInstance()); + channel->SetMessageHandler( + [plugin_pointer = plugin.get()](const auto& message, auto reply) { + plugin_pointer->HandleInitializeMethodCall(message, reply); + }); + } + + { + auto channel = + std::make_unique>( + registrar->messenger(), kVideoPlayerApiChannelCreateName, + &flutter::StandardMessageCodec::GetInstance()); + channel->SetMessageHandler( + [plugin_pointer = plugin.get()](const auto& message, auto reply) { + plugin_pointer->HandleCreateMethodCall(message, reply); + }); + } + + { + auto channel = + std::make_unique>( + registrar->messenger(), kVideoPlayerApiChannelDisposeName, + &flutter::StandardMessageCodec::GetInstance()); + channel->SetMessageHandler( + [plugin_pointer = plugin.get()](const auto& message, auto reply) { + plugin_pointer->HandleDisposeMethodCall(message, reply); + }); + } + + { + auto channel = + std::make_unique>( + registrar->messenger(), kVideoPlayerApiChannelPauseName, + &flutter::StandardMessageCodec::GetInstance()); + channel->SetMessageHandler( + [plugin_pointer = plugin.get()](const auto& message, auto reply) { + plugin_pointer->HandlePauseMethodCall(message, reply); + }); + } + + { + auto channel = + std::make_unique>( + registrar->messenger(), kVideoPlayerApiChannelPlayName, + &flutter::StandardMessageCodec::GetInstance()); + channel->SetMessageHandler( + [plugin_pointer = plugin.get()](const auto& message, auto reply) { + plugin_pointer->HandlePlayMethodCall(message, reply); + }); + } + + { + auto channel = + std::make_unique>( + registrar->messenger(), kVideoPlayerApiChannelSetLoopingName, + &flutter::StandardMessageCodec::GetInstance()); + channel->SetMessageHandler( + [plugin_pointer = plugin.get()](const auto& message, auto reply) { + plugin_pointer->HandleSetLoopingMethodCall(message, reply); + }); + } + + { + auto channel = + std::make_unique>( + registrar->messenger(), kVideoPlayerApiChannelSetVolumeName, + &flutter::StandardMessageCodec::GetInstance()); + channel->SetMessageHandler( + [plugin_pointer = plugin.get()](const auto& message, auto reply) { + plugin_pointer->HandleSetVolumeMethodCall(message, reply); + }); + } + + { + auto channel = + std::make_unique>( + registrar->messenger(), kVideoPlayerApiChannelSetMixWithOthersName, + &flutter::StandardMessageCodec::GetInstance()); + channel->SetMessageHandler( + [plugin_pointer = plugin.get()](const auto& message, auto reply) { + plugin_pointer->HandleSetMixWithOthersMethodCall(message, reply); + }); + } + + { + auto channel = + std::make_unique>( + registrar->messenger(), kVideoPlayerApiChannelSetPlaybackSpeedName, + &flutter::StandardMessageCodec::GetInstance()); + channel->SetMessageHandler( + [plugin_pointer = plugin.get()](const auto& message, auto reply) { + plugin_pointer->HandleSetPlaybackSpeedMethodCall(message, reply); + }); + } + + { + auto channel = + std::make_unique>( + registrar->messenger(), kVideoPlayerApiChannelSeekToName, + &flutter::StandardMessageCodec::GetInstance()); + channel->SetMessageHandler( + [plugin_pointer = plugin.get()](const auto& message, auto reply) { + plugin_pointer->HandleSeekToMethodCall(message, reply); + }); + } + + { + auto channel = + std::make_unique>( + registrar->messenger(), kVideoPlayerApiChannelPositionName, + &flutter::StandardMessageCodec::GetInstance()); + channel->SetMessageHandler( + [plugin_pointer = plugin.get()](const auto& message, auto reply) { + plugin_pointer->HandlePositionMethodCall(message, reply); + }); + } + + registrar->AddPlugin(std::move(plugin)); +} + +void VideoPlayerPlugin::HandleInitializeMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply) { + flutter::EncodableMap result; + + result.emplace(flutter::EncodableValue(kEncodableMapkeyResult), + flutter::EncodableValue()); + reply(flutter::EncodableValue(result)); +} + +void VideoPlayerPlugin::HandleCreateMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply) { + auto meta = CreateMessage::FromMap(message); + std::string uri; + if (!meta.GetAsset().empty()) { + // todo: gets propery path of the Flutter project. + std::string flutter_project_path = "./bundle/data/"; + uri = flutter_project_path + "flutter_assets/" + meta.GetAsset(); + } else { + uri = meta.GetUri(); + } + + auto instance = std::make_unique(); + instance->buffer = std::make_unique(); + instance->texture = + std::make_unique(flutter::PixelBufferTexture( + [instance = instance.get()]( + size_t width, size_t height) -> const FlutterDesktopPixelBuffer* { + instance->buffer->width = instance->player->GetWidth(); + instance->buffer->height = instance->player->GetHeight(); + instance->buffer->buffer = instance->player->GetFrameBuffer(); + return instance->buffer.get(); + })); + const auto texture_id = + texture_registrar_->RegisterTexture(instance->texture.get()); + instance->texture_id = texture_id; + { + auto event_channel = + std::make_unique>( + plugin_registrar_->messenger(), + kVideoPlayerVideoEventsChannelName + std::to_string(texture_id), + &flutter::StandardMethodCodec::GetInstance()); + auto event_channel_handler = std::make_unique< + flutter::StreamHandlerFunctions>( + [instance = instance.get(), host = this]( + const flutter::EncodableValue* arguments, + std::unique_ptr>&& + events) + -> std::unique_ptr< + flutter::StreamHandlerError> { + instance->event_sink = std::move(events); + host->SendInitializedEventMessage(instance->texture_id); + return nullptr; + }, + [instance = instance.get()](const flutter::EncodableValue* arguments) + -> std::unique_ptr< + flutter::StreamHandlerError> { + instance->event_sink = nullptr; + return nullptr; + }); + event_channel->SetStreamHandler(std::move(event_channel_handler)); + instance->event_channel = std::move(event_channel); + } + { + auto player_handler = std::make_unique( + // OnNotifyInitialized + [texture_id, host = this]() { + host->SendInitializedEventMessage(texture_id); + }, + // OnNotifyFrameDecoded + [texture_id, host = this]() { + host->texture_registrar_->MarkTextureFrameAvailable(texture_id); + }, + // OnNotifyCompleted + [texture_id, host = this]() { + host->SendPlayCompletedEventMessage(texture_id); + }); + instance->player = + std::make_unique(uri, std::move(player_handler)); + players_[texture_id] = std::move(instance); + } + + flutter::EncodableMap value; + TextureMessage result; + result.SetTextureId(texture_id); + value.emplace(flutter::EncodableValue(kEncodableMapkeyResult), + result.ToMap()); + reply(flutter::EncodableValue(value)); +} + +void VideoPlayerPlugin::HandleDisposeMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply) { + auto parameter = TextureMessage::FromMap(message); + const auto texture_id = parameter.GetTextureId(); + flutter::EncodableMap result; + + if (players_.find(texture_id) != players_.end()) { + auto* player = players_[texture_id].get(); + player->event_sink = nullptr; + player->event_channel->SetStreamHandler(nullptr); + player->player = nullptr; + player->buffer = nullptr; + player->texture = nullptr; + players_.erase(texture_id); + texture_registrar_->UnregisterTexture(texture_id); + + result.emplace(flutter::EncodableValue(kEncodableMapkeyResult), + flutter::EncodableValue()); + } else { + auto error_message = "Couldn't find the player with texture id: " + + std::to_string(texture_id); + result.emplace(flutter::EncodableValue(kEncodableMapkeyError), + flutter::EncodableValue(WrapError(error_message))); + } + reply(flutter::EncodableValue(result)); +} + +void VideoPlayerPlugin::HandlePauseMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply) { + auto parameter = TextureMessage::FromMap(message); + const auto texture_id = parameter.GetTextureId(); + flutter::EncodableMap result; + + if (players_.find(texture_id) != players_.end()) { + players_[texture_id]->player->Pause(); + result.emplace(flutter::EncodableValue(kEncodableMapkeyResult), + flutter::EncodableValue()); + } else { + auto error_message = "Couldn't find the player with texture id: " + + std::to_string(texture_id); + result.emplace(flutter::EncodableValue(kEncodableMapkeyError), + flutter::EncodableValue(WrapError(error_message))); + } + reply(flutter::EncodableValue(result)); +} + +void VideoPlayerPlugin::HandlePlayMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply) { + auto parameter = TextureMessage::FromMap(message); + const auto texture_id = parameter.GetTextureId(); + flutter::EncodableMap result; + + if (players_.find(texture_id) != players_.end()) { + players_[texture_id]->player->Play(); + result.emplace(flutter::EncodableValue(kEncodableMapkeyResult), + flutter::EncodableValue()); + } else { + auto error_message = "Couldn't find the player with texture id: " + + std::to_string(texture_id); + result.emplace(flutter::EncodableValue(kEncodableMapkeyError), + flutter::EncodableValue(WrapError(error_message))); + } + reply(flutter::EncodableValue(result)); +} + +void VideoPlayerPlugin::HandleSetLoopingMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply) { + auto parameter = LoopingMessage::FromMap(message); + const auto texture_id = parameter.GetTextureId(); + flutter::EncodableMap result; + + if (players_.find(texture_id) != players_.end()) { + players_[texture_id]->player->SetAutoRepeat(parameter.GetIsLooping()); + result.emplace(flutter::EncodableValue(kEncodableMapkeyResult), + flutter::EncodableValue()); + } else { + auto error_message = "Couldn't find the player with texture id: " + + std::to_string(texture_id); + result.emplace(flutter::EncodableValue(kEncodableMapkeyError), + flutter::EncodableValue(WrapError(error_message))); + } + reply(flutter::EncodableValue(result)); +} + +void VideoPlayerPlugin::HandleSetVolumeMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply) { + auto parameter = VolumeMessage::FromMap(message); + const auto texture_id = parameter.GetTextureId(); + flutter::EncodableMap result; + + if (players_.find(texture_id) != players_.end()) { + players_[texture_id]->player->SetVolume(parameter.GetVolume()); + result.emplace(flutter::EncodableValue(kEncodableMapkeyResult), + flutter::EncodableValue()); + } else { + auto error_message = "Couldn't find the player with texture id: " + + std::to_string(texture_id); + result.emplace(flutter::EncodableValue(kEncodableMapkeyError), + flutter::EncodableValue(WrapError(error_message))); + } + reply(flutter::EncodableValue(result)); +} + +void VideoPlayerPlugin::HandleSetMixWithOthersMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply) { + // todo: implements here. + + flutter::EncodableMap result; + result.emplace(flutter::EncodableValue(kEncodableMapkeyResult), + flutter::EncodableValue()); + reply(flutter::EncodableValue(result)); +} + +void VideoPlayerPlugin::HandlePositionMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply) { + auto parameter = TextureMessage::FromMap(message); + const auto texture_id = parameter.GetTextureId(); + flutter::EncodableMap result; + + if (players_.find(texture_id) != players_.end()) { + PositionMessage send_message; + send_message.SetTextureId(texture_id); + send_message.SetPosition( + players_[texture_id]->player->GetCurrentPosition()); + result.emplace(flutter::EncodableValue(kEncodableMapkeyResult), + send_message.ToMap()); + } else { + auto error_message = "Couldn't find the player with texture id: " + + std::to_string(texture_id); + result.emplace(flutter::EncodableValue(kEncodableMapkeyError), + flutter::EncodableValue(WrapError(error_message))); + } + reply(flutter::EncodableValue(result)); +} + +void VideoPlayerPlugin::HandleSetPlaybackSpeedMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply) { + auto parameter = PlaybackSpeedMessage::FromMap(message); + const auto texture_id = parameter.GetTextureId(); + flutter::EncodableMap result; + + if (players_.find(texture_id) != players_.end()) { + players_[texture_id]->player->SetPlaybackRate(parameter.GetSpeed()); + result.emplace(flutter::EncodableValue(kEncodableMapkeyResult), + flutter::EncodableValue()); + } else { + auto error_message = "Couldn't find the player with texture id: " + + std::to_string(texture_id); + result.emplace(flutter::EncodableValue(kEncodableMapkeyError), + flutter::EncodableValue(WrapError(error_message))); + } + reply(flutter::EncodableValue(result)); +} + +void VideoPlayerPlugin::HandleSeekToMethodCall( + const flutter::EncodableValue& message, + flutter::MessageReply reply) { + auto parameter = PositionMessage::FromMap(message); + const auto texture_id = parameter.GetTextureId(); + flutter::EncodableMap result; + + if (players_.find(texture_id) != players_.end()) { + players_[texture_id]->player->SetSeek(parameter.GetPosition()); + result.emplace(flutter::EncodableValue(kEncodableMapkeyResult), + flutter::EncodableValue()); + } else { + auto error_message = "Couldn't find the player with texture id: " + + std::to_string(texture_id); + result.emplace(flutter::EncodableValue(kEncodableMapkeyError), + flutter::EncodableValue(WrapError(error_message))); + } + reply(flutter::EncodableValue(result)); +} + +void VideoPlayerPlugin::SendInitializedEventMessage(int64_t texture_id) { + if (players_.find(texture_id) == players_.end() || + !players_[texture_id]->event_sink) { + return; + } + + auto duration = players_[texture_id]->player->GetDuration(); + auto width = players_[texture_id]->player->GetWidth(); + auto height = players_[texture_id]->player->GetHeight(); + flutter::EncodableMap encodables = { + {flutter::EncodableValue("event"), + flutter::EncodableValue("initialized")}, + {flutter::EncodableValue("duration"), flutter::EncodableValue(duration)}, + {flutter::EncodableValue("width"), flutter::EncodableValue(width)}, + {flutter::EncodableValue("height"), flutter::EncodableValue(height)}}; + flutter::EncodableValue event(encodables); + players_[texture_id]->event_sink->Success(event); +} + +void VideoPlayerPlugin::SendPlayCompletedEventMessage(int64_t texture_id) { + if (players_.find(texture_id) == players_.end() || + !players_[texture_id]->event_sink) { + return; + } + + flutter::EncodableMap encodables = { + {flutter::EncodableValue("event"), flutter::EncodableValue("completed")}}; + flutter::EncodableValue event(encodables); + players_[texture_id]->event_sink->Success(event); +} + +flutter::EncodableValue VideoPlayerPlugin::WrapError( + const std::string& message, const std::string& code, + const std::string& details) { + flutter::EncodableMap map = { + {flutter::EncodableValue("message"), flutter::EncodableValue(message)}, + {flutter::EncodableValue("code"), flutter::EncodableValue(code)}, + {flutter::EncodableValue("details"), flutter::EncodableValue(details)}}; + return flutter::EncodableValue(map); +} + +} // namespace + +void VideoPlayerElinuxPluginRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + VideoPlayerPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +} diff --git a/packages/video_player/elinux/video_player_stream_handler.h b/packages/video_player/elinux/video_player_stream_handler.h new file mode 100644 index 0000000..cf63c4a --- /dev/null +++ b/packages/video_player/elinux/video_player_stream_handler.h @@ -0,0 +1,32 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_VIDEO_PLAYER_STREAM_HANDLER_H_ +#define PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_VIDEO_PLAYER_STREAM_HANDLER_H_ + +class VideoPlayerStreamHandler { + public: + VideoPlayerStreamHandler() = default; + virtual ~VideoPlayerStreamHandler() = default; + + // Prevent copying. + VideoPlayerStreamHandler(VideoPlayerStreamHandler const&) = delete; + VideoPlayerStreamHandler& operator=(VideoPlayerStreamHandler const&) = delete; + + // Notifies the completion of initializing the video player. + void OnNotifyInitialized() { OnNotifyInitializedInternal(); } + + // Notifies the completion of decoding a video frame. + void OnNotifyFrameDecoded() { OnNotifyFrameDecodedInternal(); } + + // Notifies the completion of playing a video. + void OnNotifyCompleted() { OnNotifyCompletedInternal(); } + + protected: + virtual void OnNotifyInitializedInternal() = 0; + virtual void OnNotifyFrameDecodedInternal() = 0; + virtual void OnNotifyCompletedInternal() = 0; +}; + +#endif // PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_VIDEO_PLAYER_STREAM_HANDLER_H_ diff --git a/packages/video_player/elinux/video_player_stream_handler_impl.h b/packages/video_player/elinux/video_player_stream_handler_impl.h new file mode 100644 index 0000000..7b955b0 --- /dev/null +++ b/packages/video_player/elinux/video_player_stream_handler_impl.h @@ -0,0 +1,58 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_VIDEO_PLAYER_STREAM_HANDLER_IMPL_H_ +#define PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_VIDEO_PLAYER_STREAM_HANDLER_IMPL_H_ + +#include + +#include "video_player_stream_handler.h" + +class VideoPlayerStreamHandlerImpl : public VideoPlayerStreamHandler { + public: + using OnNotifyInitialized = std::function; + using OnNotifyFrameDecoded = std::function; + using OnNotifyCompleted = std::function; + + VideoPlayerStreamHandlerImpl(OnNotifyInitialized on_notify_initialized, + OnNotifyFrameDecoded on_notify_frame_decoded, + OnNotifyCompleted on_notify_completed) + : on_notify_initialized_(on_notify_initialized), + on_notify_frame_decoded_(on_notify_frame_decoded), + on_notify_completed_(on_notify_completed) {} + virtual ~VideoPlayerStreamHandlerImpl() = default; + + // Prevent copying. + VideoPlayerStreamHandlerImpl(VideoPlayerStreamHandlerImpl const&) = delete; + VideoPlayerStreamHandlerImpl& operator=(VideoPlayerStreamHandlerImpl const&) = + delete; + + protected: + // |VideoPlayerStreamHandler| + void OnNotifyInitializedInternal() { + if (on_notify_initialized_) { + on_notify_initialized_(); + } + } + + // |VideoPlayerStreamHandler| + void OnNotifyFrameDecodedInternal() { + if (on_notify_frame_decoded_) { + on_notify_frame_decoded_(); + } + } + + // |VideoPlayerStreamHandler| + void OnNotifyCompletedInternal() { + if (on_notify_completed_) { + on_notify_completed_(); + } + } + + OnNotifyInitialized on_notify_initialized_; + OnNotifyFrameDecoded on_notify_frame_decoded_; + OnNotifyCompleted on_notify_completed_; +}; + +#endif // PACKAGES_VIDEO_PLAYER_VIDEO_PLAYER_ELINUX_VIDEO_PLAYER_STREAM_HANDLER_IMPL_H_ diff --git a/packages/video_player/example/.gitignore b/packages/video_player/example/.gitignore new file mode 100644 index 0000000..d3e68fd --- /dev/null +++ b/packages/video_player/example/.gitignore @@ -0,0 +1 @@ +lib/generated_plugin_registrant.dart diff --git a/packages/video_player/example/README.md b/packages/video_player/example/README.md new file mode 100644 index 0000000..87f91a5 --- /dev/null +++ b/packages/video_player/example/README.md @@ -0,0 +1,8 @@ +# video_player_example + +Demonstrates how to use the video_player plugin. + +## Getting Started + +For help getting started with Flutter for eLinux, view our online +[documentation](https://github.com/sony/flutter-elinux/wiki). diff --git a/packages/video_player/example/elinux/CMakeLists.txt b/packages/video_player/example/elinux/CMakeLists.txt new file mode 100644 index 0000000..853f863 --- /dev/null +++ b/packages/video_player/example/elinux/CMakeLists.txt @@ -0,0 +1,110 @@ +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +set(BINARY_NAME "video_player_elinux_example") + +cmake_policy(SET CMP0079 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + + # Basically we use this include when we got the following error: + # fatal error: 'bits/c++config.h' file not found + if(FLUTTER_TARGET_PLATFORM_SYSROOT) + include_directories(SYSTEM ${FLUTTER_SYSTEM_INCLUDE_DIRECTORIES}) + endif() +endif() + +# Configure build options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Configure build option to target backend. +if (NOT FLUTTER_TARGET_BACKEND_TYPE) + set(FLUTTER_TARGET_BACKEND_TYPE "wayland" CACHE + STRING "Flutter target backend type" FORCE) + set_property(CACHE FLUTTER_TARGET_BACKEND_TYPE PROPERTY STRINGS + "wayland" "gbm" "eglstream" "x11") +endif() + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +install(FILES "${FLUTTER_EMBEDDER_LIBRARY}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/video_player/example/elinux/flutter/CMakeLists.txt b/packages/video_player/example/elinux/flutter/CMakeLists.txt new file mode 100644 index 0000000..376be86 --- /dev/null +++ b/packages/video_player/example/elinux/flutter/CMakeLists.txt @@ -0,0 +1,108 @@ +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_engine.so") +if(FLUTTER_TARGET_BACKEND_TYPE MATCHES "gbm") + set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_gbm.so") +elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "eglstream") + set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_eglstream.so") +elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "x11") + set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_x11.so") +else() + set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_wayland.so") +endif() + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_EMBEDDER_LIBRARY ${FLUTTER_EMBEDDER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/elinux/" PARENT_SCOPE) +set(AOT_LIBRARY "${EPHEMERAL_DIR}/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_plugin_registrar.h" + "flutter_messenger.h" + "flutter_texture_registrar.h" + "flutter_elinux.h" + "flutter_platform_views.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE "${FLUTTER_EMBEDDER_LIBRARY}") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + "${FLUTTER_EMBEDDER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/video_player/example/elinux/flutter/generated_plugin_registrant.h b/packages/video_player/example/elinux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..a31c23c --- /dev/null +++ b/packages/video_player/example/elinux/flutter/generated_plugin_registrant.h @@ -0,0 +1,13 @@ +// +// Generated file. Do not edit. +// + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/video_player/example/elinux/flutter/generated_plugins.cmake b/packages/video_player/example/elinux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..ab1e67f --- /dev/null +++ b/packages/video_player/example/elinux/flutter/generated_plugins.cmake @@ -0,0 +1,16 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + video_player_elinux +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/elinux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) diff --git a/packages/video_player/example/elinux/runner/CMakeLists.txt b/packages/video_player/example/elinux/runner/CMakeLists.txt new file mode 100644 index 0000000..8bb1779 --- /dev/null +++ b/packages/video_player/example/elinux/runner/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +if(FLUTTER_TARGET_BACKEND_TYPE MATCHES "gbm") + add_definitions(-DFLUTTER_TARGET_BACKEND_GBM) +elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "eglstream") + add_definitions(-DFLUTTER_TARGET_BACKEND_EGLSTREAM) +elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "x11") + add_definitions(-DFLUTTER_TARGET_BACKEND_X11) +else() + add_definitions(-DFLUTTER_TARGET_BACKEND_WAYLAND) +endif() + +add_executable(${BINARY_NAME} + "flutter_window.cc" + "main.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) +apply_standard_settings(${BINARY_NAME}) +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/video_player/example/elinux/runner/command_options.h b/packages/video_player/example/elinux/runner/command_options.h new file mode 100644 index 0000000..977cdc8 --- /dev/null +++ b/packages/video_player/example/elinux/runner/command_options.h @@ -0,0 +1,367 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMMAND_OPTIONS_ +#define COMMAND_OPTIONS_ + +#include +#include +#include +#include +#include +#include +#include + +// todo: Supports other types besides int, string. + +namespace commandline { + +namespace { +constexpr char kOptionStyleNormal[] = "--"; +constexpr char kOptionStyleShort[] = "-"; +constexpr char kOptionValueForHelpMessage[] = "="; +} // namespace + +class Exception : public std::exception { + public: + Exception(const std::string& msg) : msg_(msg) {} + ~Exception() throw() {} + + const char* what() const throw() { return msg_.c_str(); } + + private: + std::string msg_; +}; + +class CommandOptions { + public: + CommandOptions() = default; + ~CommandOptions() = default; + + void AddWithoutValue(const std::string& name, const std::string& short_name, + const std::string& description, bool required) { + Add(name, short_name, description, "", + ReaderString(), required, false); + } + + void AddInt(const std::string& name, const std::string& short_name, + const std::string& description, const int& default_value, + bool required) { + Add(name, short_name, description, default_value, + ReaderInt(), required, true); + } + + void AddString(const std::string& name, const std::string& short_name, + const std::string& description, + const std::string& default_value, bool required) { + Add(name, short_name, description, default_value, + ReaderString(), required, true); + } + + template + void Add(const std::string& name, const std::string& short_name, + const std::string& description, const T default_value, + F reader = F(), bool required = true, bool required_value = true) { + if (options_.find(name) != options_.end()) { + std::cerr << "Already registered option: " << name << std::endl; + return; + } + + if (lut_short_options_.find(short_name) != lut_short_options_.end()) { + std::cerr << short_name << "is already registered" << std::endl; + return; + } + lut_short_options_[short_name] = name; + + options_[name] = std::make_unique>( + name, short_name, description, default_value, reader, required, + required_value); + + // register to show help message. + registration_order_options_.push_back(options_[name].get()); + } + + bool Exist(const std::string& name) { + auto itr = options_.find(name); + return itr != options_.end() && itr->second->HasValue(); + } + + template + const T& GetValue(const std::string& name) { + auto itr = options_.find(name); + if (itr == options_.end()) { + throw Exception("Not found: " + name); + } + + auto* option_value = dynamic_cast*>(itr->second.get()); + if (!option_value) { + throw Exception("Type mismatch: " + name); + } + return option_value->GetValue(); + } + + bool Parse(int argc, const char* const* argv) { + if (argc < 1) { + errors_.push_back("No options"); + return false; + } + + command_name_ = argv[0]; + for (auto i = 1; i < argc; i++) { + const std::string arg(argv[i]); + + // normal options: e.g. --bundle=/data/sample/bundle --fullscreen + if (arg.length() > 2 && + arg.substr(0, 2).compare(kOptionStyleNormal) == 0) { + const size_t option_value_len = arg.find("=") != std::string::npos + ? (arg.length() - arg.find("=")) + : 0; + const bool has_value = option_value_len != 0; + std::string option_name = + arg.substr(2, arg.length() - 2 - option_value_len); + + if (options_.find(option_name) == options_.end()) { + errors_.push_back("Not found option: " + option_name); + continue; + } + + if (!has_value && options_[option_name]->IsRequiredValue()) { + errors_.push_back(option_name + " requres an option value"); + continue; + } + + if (has_value && !options_[option_name]->IsRequiredValue()) { + errors_.push_back(option_name + " doesn't requres an option value"); + continue; + } + + if (has_value) { + SetOptionValue(option_name, arg.substr(arg.find("=") + 1)); + } else { + SetOption(option_name); + } + } + // short options: e.g. -f /foo/file.txt -h 640 -abc + else if (arg.length() > 1 && + arg.substr(0, 1).compare(kOptionStyleShort) == 0) { + for (size_t j = 1; j < arg.length(); j++) { + const std::string option_name{argv[i][j]}; + + if (lut_short_options_.find(option_name) == + lut_short_options_.end()) { + errors_.push_back("Not found short option: " + option_name); + break; + } + + if (j == arg.length() - 1 && + options_[lut_short_options_[option_name]]->IsRequiredValue()) { + if (i == argc - 1) { + errors_.push_back("Invalid format option: " + option_name); + break; + } + SetOptionValue(lut_short_options_[option_name], argv[++i]); + } else { + SetOption(lut_short_options_[option_name]); + } + } + } else { + errors_.push_back("Invalid format option: " + arg); + } + } + + for (size_t i = 0; i < registration_order_options_.size(); i++) { + if (registration_order_options_[i]->IsRequired() && + !registration_order_options_[i]->HasValue()) { + errors_.push_back( + std::string(registration_order_options_[i]->GetName()) + + " option is mandatory."); + } + } + + return errors_.size() == 0; + } + + std::string GetError() { return errors_.size() > 0 ? errors_[0] : ""; } + + std::vector& GetErrors() { return errors_; } + + std::string ShowHelp() { + std::ostringstream ostream; + + ostream << "Usage: " << command_name_ << " "; + for (size_t i = 0; i < registration_order_options_.size(); i++) { + if (registration_order_options_[i]->IsRequired()) { + ostream << registration_order_options_[i]->GetHelpShortMessage() << " "; + } + } + ostream << std::endl; + + ostream << "Global options:" << std::endl; + size_t max_name_len = 0; + for (size_t i = 0; i < registration_order_options_.size(); i++) { + max_name_len = std::max( + max_name_len, registration_order_options_[i]->GetName().length()); + } + + for (size_t i = 0; i < registration_order_options_.size(); i++) { + if (!registration_order_options_[i]->GetShortName().empty()) { + ostream << kOptionStyleShort + << registration_order_options_[i]->GetShortName() << ", "; + } else { + ostream << std::string(4, ' '); + } + + size_t index_adjust = 0; + constexpr int kSpacerNum = 5; + auto need_value = registration_order_options_[i]->IsRequiredValue(); + ostream << kOptionStyleNormal + << registration_order_options_[i]->GetName(); + if (need_value) { + ostream << kOptionValueForHelpMessage; + index_adjust += std::string(kOptionValueForHelpMessage).length(); + } + ostream << std::string( + max_name_len + kSpacerNum - index_adjust - + registration_order_options_[i]->GetName().length(), + ' '); + ostream << registration_order_options_[i]->GetDescription() << std::endl; + } + + return ostream.str(); + } + + private: + struct ReaderInt { + int operator()(const std::string& value) { return std::stoi(value); } + }; + + struct ReaderString { + std::string operator()(const std::string& value) { return value; } + }; + + class Option { + public: + Option(const std::string& name, const std::string& short_name, + const std::string& description, bool required, bool required_value) + : name_(name), + short_name_(short_name), + description_(description), + is_required_(required), + is_required_value_(required_value), + value_set_(false){}; + virtual ~Option() = default; + + const std::string& GetName() const { return name_; }; + + const std::string& GetShortName() const { return short_name_; }; + + const std::string& GetDescription() const { return description_; }; + + const std::string GetHelpShortMessage() const { + std::string message = kOptionStyleNormal + name_; + if (is_required_value_) { + message += kOptionValueForHelpMessage; + } + return message; + } + + bool IsRequired() const { return is_required_; }; + + bool IsRequiredValue() const { return is_required_value_; }; + + void Set() { value_set_ = true; }; + + virtual bool SetValue(const std::string& value) = 0; + + virtual bool HasValue() const = 0; + + protected: + std::string name_; + std::string short_name_; + std::string description_; + bool is_required_; + bool is_required_value_; + bool value_set_; + }; + + template + class OptionValue : public Option { + public: + OptionValue(const std::string& name, const std::string& short_name, + const std::string& description, const T& default_value, + bool required, bool required_value) + : Option(name, short_name, description, required, required_value), + default_value_(default_value), + value_(default_value){}; + virtual ~OptionValue() = default; + + bool SetValue(const std::string& value) { + value_ = Read(value); + value_set_ = true; + return true; + } + + bool HasValue() const { return value_set_; } + + const T& GetValue() const { return value_; } + + protected: + virtual T Read(const std::string& s) = 0; + + T default_value_; + T value_; + }; + + template + class OptionValueReader : public OptionValue { + public: + OptionValueReader(const std::string& name, const std::string& short_name, + const std::string& description, const T default_value, + F reader, bool required, bool required_value) + : OptionValue(name, short_name, description, default_value, required, + required_value), + reader_(reader) {} + ~OptionValueReader() = default; + + private: + T Read(const std::string& value) { return reader_(value); } + + F reader_; + }; + + bool SetOption(const std::string& name) { + auto itr = options_.find(name); + if (itr == options_.end()) { + errors_.push_back("Unknown option: " + name); + return false; + } + + itr->second->Set(); + return true; + } + + bool SetOptionValue(const std::string& name, const std::string& value) { + auto itr = options_.find(name); + if (itr == options_.end()) { + errors_.push_back("Unknown option: " + name); + return false; + } + + if (!itr->second->SetValue(value)) { + errors_.push_back("Invalid option value: " + name + " = " + value); + return false; + } + return true; + } + + std::string command_name_; + std::unordered_map> options_; + std::unordered_map lut_short_options_; + std::vector registration_order_options_; + std::vector errors_; +}; + +} // namespace commandline + +#endif // COMMAND_OPTIONS_ diff --git a/packages/video_player/example/elinux/runner/flutter_embedder_options.h b/packages/video_player/example/elinux/runner/flutter_embedder_options.h new file mode 100644 index 0000000..51e378f --- /dev/null +++ b/packages/video_player/example/elinux/runner/flutter_embedder_options.h @@ -0,0 +1,103 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_EMBEDDER_OPTIONS_ +#define FLUTTER_EMBEDDER_OPTIONS_ + +#include + +#include + +#include "command_options.h" + +class FlutterEmbedderOptions { + public: + FlutterEmbedderOptions() { + options_.AddString("bundle", "b", "Path to Flutter app bundle", "./bundle", + true); + options_.AddWithoutValue("no-cursor", "n", "No mouse cursor/pointer", + false); +#if defined(FLUTTER_TARGET_BACKEND_GBM) || \ + defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) + // no more options. +#elif defined(FLUTTER_TARGET_BACKEND_X11) + options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", + false); + options_.AddInt("width", "w", "Flutter app window width", 1280, false); + options_.AddInt("height", "h", "Flutter app window height", 720, false); +#else // FLUTTER_TARGET_BACKEND_WAYLAND + options_.AddWithoutValue("onscreen-keyboard", "k", + "Enable on-screen keyboard", false); + options_.AddWithoutValue("window-decoration", "d", + "Enable window decorations", false); + options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", + false); + options_.AddInt("width", "w", "Flutter app window width", 1280, false); + options_.AddInt("height", "h", "Flutter app window height", 720, false); +#endif + } + ~FlutterEmbedderOptions() = default; + + bool Parse(int argc, char** argv) { + if (!options_.Parse(argc, argv)) { + std::cerr << options_.GetError() << std::endl; + std::cout << options_.ShowHelp(); + return false; + } + + bundle_path_ = options_.GetValue("bundle"); + use_mouse_cursor_ = !options_.Exist("no-cursor"); + +#if defined(FLUTTER_TARGET_BACKEND_GBM) || \ + defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) + use_onscreen_keyboard_ = false; + use_window_decoration_ = false; + window_view_mode_ = flutter::FlutterViewController::ViewMode::kFullscreen; +#elif defined(FLUTTER_TARGET_BACKEND_X11) + use_onscreen_keyboard_ = false; + use_window_decoration_ = false; + window_view_mode_ = + options_.Exist("fullscreen") + ? flutter::FlutterViewController::ViewMode::kFullscreen + : flutter::FlutterViewController::ViewMode::kNormal; + window_width_ = options_.GetValue("width"); + window_height_ = options_.GetValue("height"); +#else // FLUTTER_TARGET_BACKEND_WAYLAND + use_onscreen_keyboard_ = options_.Exist("onscreen-keyboard"); + use_window_decoration_ = options_.Exist("window-decoration"); + window_view_mode_ = + options_.Exist("fullscreen") + ? flutter::FlutterViewController::ViewMode::kFullscreen + : flutter::FlutterViewController::ViewMode::kNormal; + window_width_ = options_.GetValue("width"); + window_height_ = options_.GetValue("height"); +#endif + + return true; + } + + std::string BundlePath() const { return bundle_path_; } + bool IsUseMouseCursor() const { return use_mouse_cursor_; } + bool IsUseOnscreenKeyboard() const { return use_onscreen_keyboard_; } + bool IsUseWindowDecoraation() const { return use_window_decoration_; } + flutter::FlutterViewController::ViewMode WindowViewMode() const { + return window_view_mode_; + } + int WindowWidth() const { return window_width_; } + int WindowHeight() const { return window_height_; } + + private: + commandline::CommandOptions options_; + + std::string bundle_path_; + bool use_mouse_cursor_ = true; + bool use_onscreen_keyboard_ = false; + bool use_window_decoration_ = false; + flutter::FlutterViewController::ViewMode window_view_mode_ = + flutter::FlutterViewController::ViewMode::kNormal; + int window_width_ = 1280; + int window_height_ = 720; +}; + +#endif // FLUTTER_EMBEDDER_OPTIONS_ diff --git a/packages/video_player/example/elinux/runner/flutter_window.cc b/packages/video_player/example/elinux/runner/flutter_window.cc new file mode 100644 index 0000000..0c5b639 --- /dev/null +++ b/packages/video_player/example/elinux/runner/flutter_window.cc @@ -0,0 +1,79 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter_window.h" + +#include +#include +#include +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow( + const flutter::FlutterViewController::ViewProperties view_properties, + const flutter::DartProject project) + : view_properties_(view_properties), project_(project) {} + +bool FlutterWindow::OnCreate() { + flutter_view_controller_ = std::make_unique( + view_properties_, project_); + + // Ensure that basic setup of the controller was successful. + if (!flutter_view_controller_->engine() || + !flutter_view_controller_->view()) { + return false; + } + + // Register Flutter plugins. + RegisterPlugins(flutter_view_controller_->engine()); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_view_controller_) { + flutter_view_controller_ = nullptr; + } +} + +void FlutterWindow::Run() { + // Main loop. + auto next_flutter_event_time = + std::chrono::steady_clock::time_point::clock::now(); + while (flutter_view_controller_->view()->DispatchEvent()) { + // Wait until the next event. + { + auto wait_duration = + std::max(std::chrono::nanoseconds(0), + next_flutter_event_time - + std::chrono::steady_clock::time_point::clock::now()); + std::this_thread::sleep_for( + std::chrono::duration_cast(wait_duration)); + } + + // Processes any pending events in the Flutter engine, and returns the + // number of nanoseconds until the next scheduled event (or max, if none). + auto wait_duration = flutter_view_controller_->engine()->ProcessMessages(); + { + auto next_event_time = std::chrono::steady_clock::time_point::max(); + if (wait_duration != std::chrono::nanoseconds::max()) { + next_event_time = + std::min(next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + wait_duration); + } else { + // Wait for the next frame if no events. + auto frame_rate = flutter_view_controller_->view()->GetFrameRate(); + next_event_time = std::min( + next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + std::chrono::milliseconds( + static_cast(std::trunc(1000000.0 / frame_rate)))); + } + next_flutter_event_time = + std::max(next_flutter_event_time, next_event_time); + } + } +} diff --git a/packages/video_player/example/elinux/runner/flutter_window.h b/packages/video_player/example/elinux/runner/flutter_window.h new file mode 100644 index 0000000..20b9cb8 --- /dev/null +++ b/packages/video_player/example/elinux/runner/flutter_window.h @@ -0,0 +1,34 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_WINDOW_ +#define FLUTTER_WINDOW_ + +#include +#include + +#include + +class FlutterWindow { + public: + explicit FlutterWindow( + const flutter::FlutterViewController::ViewProperties view_properties, + const flutter::DartProject project); + ~FlutterWindow() = default; + + // Prevent copying. + FlutterWindow(FlutterWindow const&) = delete; + FlutterWindow& operator=(FlutterWindow const&) = delete; + + bool OnCreate(); + void OnDestroy(); + void Run(); + + private: + flutter::FlutterViewController::ViewProperties view_properties_; + flutter::DartProject project_; + std::unique_ptr flutter_view_controller_; +}; + +#endif // FLUTTER_WINDOW_ diff --git a/packages/video_player/example/elinux/runner/main.cc b/packages/video_player/example/elinux/runner/main.cc new file mode 100644 index 0000000..9c9c91d --- /dev/null +++ b/packages/video_player/example/elinux/runner/main.cc @@ -0,0 +1,46 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include +#include +#include + +#include "flutter_embedder_options.h" +#include "flutter_window.h" + +int main(int argc, char** argv) { + FlutterEmbedderOptions options; + if (!options.Parse(argc, argv)) { + return 0; + } + + // Creates the Flutter project. + const auto bundle_path = options.BundlePath(); + const std::wstring fl_path(bundle_path.begin(), bundle_path.end()); + flutter::DartProject project(fl_path); + auto command_line_arguments = std::vector(); + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + flutter::FlutterViewController::ViewProperties view_properties = {}; + view_properties.width = options.WindowWidth(); + view_properties.height = options.WindowHeight(); + view_properties.view_mode = options.WindowViewMode(); + view_properties.use_mouse_cursor = options.IsUseMouseCursor(); + view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard(); + view_properties.use_window_decoration = options.IsUseWindowDecoraation(); + + // The Flutter instance hosted by this window. + FlutterWindow window(view_properties, project); + if (!window.OnCreate()) { + std::cerr << "Failed to create a Flutter window." << std::endl; + return 0; + } + window.Run(); + window.OnDestroy(); + + return 0; +} diff --git a/packages/video_player/example/lib/main.dart b/packages/video_player/example/lib/main.dart new file mode 100644 index 0000000..ee9dfb1 --- /dev/null +++ b/packages/video_player/example/lib/main.dart @@ -0,0 +1,182 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +/// An example of using the plugin, controlling lifecycle and playback of the +/// video. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:video_player/video_player.dart'; + +void main() { + runApp( + MaterialApp( + home: _App(), + ), + ); +} + +class _App extends StatelessWidget { + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 1, + child: Scaffold( + key: const ValueKey('home_page'), + appBar: AppBar( + title: const Text('Video player example'), + bottom: const TabBar( + isScrollable: true, + tabs: [ + Tab( + icon: Icon(Icons.cloud), + text: "Remote", + ), + ], + ), + ), + body: TabBarView( + children: [ + _BumbleBeeRemoteVideo(), + ], + ), + ), + ); + } +} + +class _BumbleBeeRemoteVideo extends StatefulWidget { + @override + _BumbleBeeRemoteVideoState createState() => _BumbleBeeRemoteVideoState(); +} + +class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { + late VideoPlayerController _controller; + + @override + void initState() { + super.initState(); + _controller = VideoPlayerController.network( + 'https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_cropped_multilingual.webm', + videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), + ); + + _controller.addListener(() { + setState(() {}); + }); + _controller.setLooping(true); + _controller.initialize(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + Container(padding: const EdgeInsets.only(top: 20.0)), + const Text('With remote video'), + Container( + padding: const EdgeInsets.all(20), + child: AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + VideoPlayer(_controller), + ClosedCaption(text: _controller.value.caption.text), + _ControlsOverlay(controller: _controller), + VideoProgressIndicator(_controller, allowScrubbing: true), + ], + ), + ), + ), + ], + ), + ); + } +} + +class _ControlsOverlay extends StatelessWidget { + const _ControlsOverlay({Key? key, required this.controller}) + : super(key: key); + + static const _examplePlaybackRates = [ + 0.25, + 0.5, + 1.0, + 1.5, + 2.0, + 3.0, + 5.0, + 10.0, + ]; + + final VideoPlayerController controller; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + AnimatedSwitcher( + duration: Duration(milliseconds: 50), + reverseDuration: Duration(milliseconds: 200), + child: controller.value.isPlaying + ? SizedBox.shrink() + : Container( + color: Colors.black26, + child: Center( + child: Icon( + Icons.play_arrow, + color: Colors.white, + size: 100.0, + ), + ), + ), + ), + GestureDetector( + onTap: () { + controller.value.isPlaying ? controller.pause() : controller.play(); + }, + ), + Align( + alignment: Alignment.topRight, + child: PopupMenuButton( + initialValue: controller.value.playbackSpeed, + tooltip: 'Playback speed', + onSelected: (speed) { + controller.setPlaybackSpeed(speed); + }, + itemBuilder: (context) { + return [ + for (final speed in _examplePlaybackRates) + PopupMenuItem( + value: speed, + child: Text('${speed}x'), + ) + ]; + }, + child: Padding( + padding: const EdgeInsets.symmetric( + // Using less vertical padding as the text is also longer + // horizontally, so it feels like it would need more spacing + // horizontally (matching the aspect ratio of the video). + vertical: 12, + horizontal: 16, + ), + child: Text('${controller.value.playbackSpeed}x'), + ), + ), + ), + ], + ); + } +} diff --git a/packages/video_player/example/pubspec.yaml b/packages/video_player/example/pubspec.yaml new file mode 100644 index 0000000..6e174d5 --- /dev/null +++ b/packages/video_player/example/pubspec.yaml @@ -0,0 +1,21 @@ +name: video_player_example +description: Demonstrates how to use the video_player plugin. +publish_to: none + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=2.0.0" + +dependencies: + flutter: + sdk: flutter + video_player: ^2.1.12 + video_player_elinux: + path: ../ + +dev_dependencies: + pedantic: ^1.10.0 + test: any + +flutter: + uses-material-design: true diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml new file mode 100644 index 0000000..cc3579f --- /dev/null +++ b/packages/video_player/pubspec.yaml @@ -0,0 +1,20 @@ +name: video_player_elinux +description: Flutter plugin for displaying inline video with other Flutter widgets on Embedded Linux. +version: 0.9.0 +homepage: https://github.com/sony/flutter-elinux-plugins +repository: https://github.com/sony/flutter-elinux-plugins/tree/main/packages/video_player/video_player + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.20.0" + +dependencies: + flutter: + sdk: flutter + video_player_platform_interface: ^4.1.0 + +flutter: + plugin: + platforms: + elinux: + pluginClass: VideoPlayerElinuxPlugin