Initial commit
This commit is contained in:
+29
@@ -0,0 +1,29 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
@@ -0,0 +1,10 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: c860cba910319332564e1e9d470a17074c1f2dfd
|
||||
channel: stable
|
||||
|
||||
project_type: plugin
|
||||
@@ -0,0 +1,3 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2022 Nguyen Hoai Phong <phongnh.36@gmail.com>.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,54 @@
|
||||
# GStreamer Player Plugin
|
||||
|
||||
A GStreamer-powered alternative to Flutter's video_player that support Linux.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
Follow GStreamer's [Install on Linux](https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c) instuction
|
||||
|
||||
## Getting Started
|
||||
|
||||
To start using the plugin, copy this code or follow the example project in `example`
|
||||
|
||||
```
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gstreamer_player/flutter_gstreamer_player.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: GstPlayer(
|
||||
pipeline:
|
||||
'''rtspsrc location=
|
||||
rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4 !
|
||||
rtph264depay !
|
||||
h264parse !
|
||||
decodebin !
|
||||
videoconvert !
|
||||
video/x-raw,format=RGBA !
|
||||
appsink name=sink''',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2022, Nguyen Hoai Phong <phongnh.36@gmail.com>.
|
||||
|
||||
This library & work under this repository is licensed under MIT License.
|
||||
@@ -0,0 +1,4 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
@@ -0,0 +1,46 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Web related
|
||||
lib/generated_plugin_registrant.dart
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
@@ -0,0 +1,10 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: c860cba910319332564e1e9d470a17074c1f2dfd
|
||||
channel: stable
|
||||
|
||||
project_type: app
|
||||
@@ -0,0 +1,16 @@
|
||||
# flutter_gstreamer_player_example
|
||||
|
||||
Demonstrates how to use the flutter_gstreamer_player plugin.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
|
||||
|
||||
For help getting started with Flutter, view our
|
||||
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
@@ -0,0 +1,29 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at
|
||||
# https://dart-lang.github.io/linter/lints/index.html.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gstreamer_player/flutter_gstreamer_player.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: <Widget> [
|
||||
Expanded(
|
||||
child: GstPlayer(
|
||||
pipeline:
|
||||
'''rtspsrc location=
|
||||
rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4 !
|
||||
rtph264depay !
|
||||
h264parse !
|
||||
decodebin !
|
||||
videoconvert !
|
||||
video/x-raw,format=RGBA !
|
||||
appsink name=sink''',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: GstPlayer(
|
||||
pipeline:
|
||||
'''rtspsrc location=
|
||||
rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4 !
|
||||
rtph264depay !
|
||||
h264parse !
|
||||
decodebin !
|
||||
videoconvert !
|
||||
video/x-raw,format=RGBA !
|
||||
appsink name=sink''',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: <Widget> [
|
||||
Expanded(
|
||||
child: GstPlayer(
|
||||
pipeline:
|
||||
'''rtspsrc location=
|
||||
rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4 !
|
||||
rtph264depay !
|
||||
h264parse !
|
||||
decodebin !
|
||||
videoconvert !
|
||||
video/x-raw,format=RGBA !
|
||||
appsink name=sink''',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: GstPlayer(
|
||||
pipeline:
|
||||
'''rtspsrc location=
|
||||
rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4 !
|
||||
rtph264depay !
|
||||
h264parse !
|
||||
decodebin !
|
||||
videoconvert !
|
||||
video/x-raw,format=RGBA !
|
||||
appsink name=sink''',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
flutter/ephemeral
|
||||
@@ -0,0 +1,116 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
set(BINARY_NAME "flutter_gstreamer_player_example")
|
||||
set(APPLICATION_ID "com.example.flutter_gstreamer_player")
|
||||
|
||||
cmake_policy(SET CMP0063 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)
|
||||
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()
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||
endfunction()
|
||||
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
|
||||
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||
|
||||
# Application build
|
||||
add_executable(${BINARY_NAME}
|
||||
"main.cc"
|
||||
"my_application.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 PkgConfig::GTK)
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||
# Only the install-generated bundle's copy of the executable will launch
|
||||
# correctly, since the resources must in the right relative locations. To avoid
|
||||
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||
# the default top-level location.
|
||||
set_target_properties(${BINARY_NAME}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||
)
|
||||
|
||||
# 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)
|
||||
|
||||
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()
|
||||
@@ -0,0 +1,87 @@
|
||||
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)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
|
||||
# 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.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"fl_basic_message_channel.h"
|
||||
"fl_binary_codec.h"
|
||||
"fl_binary_messenger.h"
|
||||
"fl_dart_project.h"
|
||||
"fl_engine.h"
|
||||
"fl_json_message_codec.h"
|
||||
"fl_json_method_codec.h"
|
||||
"fl_message_codec.h"
|
||||
"fl_method_call.h"
|
||||
"fl_method_channel.h"
|
||||
"fl_method_codec.h"
|
||||
"fl_method_response.h"
|
||||
"fl_plugin_registrar.h"
|
||||
"fl_plugin_registry.h"
|
||||
"fl_standard_message_codec.h"
|
||||
"fl_standard_method_codec.h"
|
||||
"fl_string_codec.h"
|
||||
"fl_value.h"
|
||||
"fl_view.h"
|
||||
"flutter_linux.h"
|
||||
)
|
||||
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||
target_link_libraries(flutter INTERFACE
|
||||
PkgConfig::GTK
|
||||
PkgConfig::GLIB
|
||||
PkgConfig::GIO
|
||||
)
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_gstreamer_player/flutter_gstreamer_player_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) flutter_gstreamer_player_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterGstreamerPlayerPlugin");
|
||||
flutter_gstreamer_player_plugin_register_with_registrar(flutter_gstreamer_player_registrar);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void fl_register_plugins(FlPluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||
@@ -0,0 +1,16 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_gstreamer_player
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "my_application.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
g_autoptr(MyApplication) app = my_application_new();
|
||||
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "my_application.h"
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
struct _MyApplication {
|
||||
GtkApplication parent_instance;
|
||||
char** dart_entrypoint_arguments;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
// Implements GApplication::activate.
|
||||
static void my_application_activate(GApplication* application) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
GtkWindow* window =
|
||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||
|
||||
// Use a header bar when running in GNOME as this is the common style used
|
||||
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||
// desktop).
|
||||
// If running on X and not using GNOME then just use a traditional title bar
|
||||
// in case the window manager does more exotic layout, e.g. tiling.
|
||||
// If running on Wayland assume the header bar will work (may need changing
|
||||
// if future cases occur).
|
||||
gboolean use_header_bar = TRUE;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
if (GDK_IS_X11_SCREEN(screen)) {
|
||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||
use_header_bar = FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (use_header_bar) {
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||
gtk_header_bar_set_title(header_bar, "flutter_gstreamer_player_example");
|
||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||
} else {
|
||||
gtk_window_set_title(window, "flutter_gstreamer_player_example");
|
||||
}
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||
|
||||
FlView* view = fl_view_new(project);
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||
}
|
||||
|
||||
// Implements GApplication::local_command_line.
|
||||
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
// Strip out the first argument as it is the binary name.
|
||||
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||
|
||||
g_autoptr(GError) error = nullptr;
|
||||
if (!g_application_register(application, nullptr, &error)) {
|
||||
g_warning("Failed to register: %s", error->message);
|
||||
*exit_status = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_application_activate(application);
|
||||
*exit_status = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Implements GObject::dispose.
|
||||
static void my_application_dispose(GObject* object) {
|
||||
MyApplication* self = MY_APPLICATION(object);
|
||||
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void my_application_class_init(MyApplicationClass* klass) {
|
||||
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||
}
|
||||
|
||||
static void my_application_init(MyApplication* self) {}
|
||||
|
||||
MyApplication* my_application_new() {
|
||||
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||
"application-id", APPLICATION_ID,
|
||||
"flags", G_APPLICATION_NON_UNIQUE,
|
||||
nullptr));
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||
#define FLUTTER_MY_APPLICATION_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
|
||||
GtkApplication)
|
||||
|
||||
/**
|
||||
* my_application_new:
|
||||
*
|
||||
* Creates a new Flutter-based application.
|
||||
*
|
||||
* Returns: a new #MyApplication.
|
||||
*/
|
||||
MyApplication* my_application_new();
|
||||
|
||||
#endif // FLUTTER_MY_APPLICATION_H_
|
||||
@@ -0,0 +1,182 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.8.2"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_gstreamer_player:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.11"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.8"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
sdks:
|
||||
dart: ">=2.16.2 <3.0.0"
|
||||
flutter: ">=2.5.0"
|
||||
@@ -0,0 +1,84 @@
|
||||
name: flutter_gstreamer_player_example
|
||||
description: Demonstrates how to use the flutter_gstreamer_player plugin.
|
||||
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
environment:
|
||||
sdk: ">=2.16.2 <3.0.0"
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||
# dependencies can be manually updated by changing the version numbers below to
|
||||
# the latest version available on pub.dev. To see which dependencies have newer
|
||||
# versions available, run `flutter pub outdated`.
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
flutter_gstreamer_player:
|
||||
# When depending on this package from a real application you should use:
|
||||
# flutter_gstreamer_player: ^x.y.z
|
||||
# See https://dart.dev/tools/pub/dependencies#version-constraints
|
||||
# The example app is bundled with the plugin so we use a path dependency on
|
||||
# the parent directory to use the current plugin's version.
|
||||
path: ../
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^1.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter.
|
||||
flutter:
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
||||
@@ -0,0 +1,27 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility that Flutter provides. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:flutter_gstreamer_player_example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that platform version is retrieved.
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(Widget widget) => widget is Text &&
|
||||
widget.data!.startsWith('Running on:'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "gst_player.h"
|
||||
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#define GstPlayer_GstInit_ProgramName "gstreamer"
|
||||
#define GstPlayer_GstInit_Arg1 "/home/1.ogg"
|
||||
|
||||
GstPlayer::GstPlayer(const std::vector<std::string>& cmd_arguments) {
|
||||
if (cmd_arguments.empty()) {
|
||||
char arg0[] = GstPlayer_GstInit_ProgramName;
|
||||
char arg1[] = GstPlayer_GstInit_Arg1;
|
||||
char* argv[] = { &arg0[0], &arg1[0], NULL };
|
||||
int argc = (int)(sizeof(argv) / sizeof(argv[0])) - 1;
|
||||
gst_init(&argc, (char ***)&argv);
|
||||
} else {
|
||||
// TODO handle this case, pass command line arguments to gstreamer
|
||||
}
|
||||
}
|
||||
|
||||
GstPlayer::~GstPlayer() {
|
||||
// TODO Should free GStreamers stuff in destructor,
|
||||
// but when implemented, flutter complains something about texture
|
||||
// when closing application
|
||||
// freeGst();
|
||||
}
|
||||
|
||||
void GstPlayer::onVideo(VideoFrameCallback callback) {
|
||||
video_callback_ = callback;
|
||||
}
|
||||
|
||||
void GstPlayer::play(const gchar* pipelineString) {
|
||||
pipelineString_ = pipelineString;
|
||||
|
||||
// Check and free previous playing GStreamers if any
|
||||
if (sink_ != nullptr || pipeline != nullptr) {
|
||||
freeGst();
|
||||
}
|
||||
|
||||
pipeline = gst_parse_launch(
|
||||
pipelineString_.c_str(),
|
||||
nullptr);
|
||||
|
||||
sink_ = gst_bin_get_by_name(GST_BIN(pipeline), "sink");
|
||||
gst_app_sink_set_emit_signals(GST_APP_SINK(sink_), TRUE);
|
||||
g_signal_connect(sink_, "new-sample", G_CALLBACK(newSample), (gpointer)this);
|
||||
|
||||
gst_element_set_state(pipeline, GST_STATE_PLAYING);
|
||||
}
|
||||
|
||||
void GstPlayer::freeGst(void) {
|
||||
gst_element_set_state(pipeline, GST_STATE_NULL);
|
||||
gst_object_unref(sink_);
|
||||
gst_object_unref(pipeline);
|
||||
}
|
||||
|
||||
GstFlowReturn GstPlayer::newSample(GstAppSink *sink, gpointer gSelf) {
|
||||
GstSample* sample = NULL;
|
||||
GstMapInfo bufferInfo;
|
||||
|
||||
GstPlayer* self = static_cast<GstPlayer* >(gSelf);
|
||||
sample = gst_app_sink_pull_sample(GST_APP_SINK(self->sink_));
|
||||
|
||||
if(sample != NULL) {
|
||||
GstBuffer *buffer_ = gst_sample_get_buffer(sample);
|
||||
if(buffer_ != NULL) {
|
||||
gst_buffer_map(buffer_, &bufferInfo, GST_MAP_READ);
|
||||
|
||||
// Get video width and height
|
||||
GstVideoFrame vframe;
|
||||
GstVideoInfo video_info;
|
||||
GstCaps* sampleCaps = gst_sample_get_caps(sample);
|
||||
gst_video_info_from_caps(&video_info, sampleCaps);
|
||||
gst_video_frame_map (&vframe, &video_info, buffer_, GST_MAP_READ);
|
||||
|
||||
self->video_callback_(
|
||||
(uint8_t*)bufferInfo.data,
|
||||
video_info.width,
|
||||
video_info.height);
|
||||
|
||||
gst_buffer_unmap(buffer_, &bufferInfo);
|
||||
gst_video_frame_unmap(&vframe);
|
||||
}
|
||||
gst_sample_unref(sample);
|
||||
}
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
GstPlayer* GstPlayers::Get(int32_t id, std::vector<std::string> cmd_arguments) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto [it, added] = players_.try_emplace(id, nullptr);
|
||||
if (added) {
|
||||
it->second = std::make_unique<GstPlayer>(cmd_arguments);
|
||||
}
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
void GstPlayers::Dispose(int32_t id) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
players_.erase(id);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef GST_PLAYER_H_
|
||||
#define GST_PLAYER_H_
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/app/gstappsink.h>
|
||||
|
||||
typedef std::function<void(uint8_t*, int32_t, int32_t)> VideoFrameCallback;
|
||||
|
||||
class GstPlayer {
|
||||
public:
|
||||
GstPlayer(const std::vector<std::string>& cmd_arguments);
|
||||
|
||||
~GstPlayer(void);
|
||||
|
||||
void onVideo(VideoFrameCallback callback);
|
||||
|
||||
void play(const gchar* pipelineString);
|
||||
|
||||
private:
|
||||
std::string pipelineString_;
|
||||
VideoFrameCallback video_callback_;
|
||||
|
||||
GstElement *pipeline = nullptr;
|
||||
GstElement *sink_ = nullptr;
|
||||
|
||||
void freeGst(void);
|
||||
|
||||
static GstFlowReturn newSample(GstAppSink *sink, gpointer gSelf);
|
||||
};
|
||||
|
||||
class GstPlayers {
|
||||
public:
|
||||
GstPlayer* Get(int32_t id, std::vector<std::string> cmd_arguments = {});
|
||||
|
||||
void Dispose(int32_t id);
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::map<int32_t, std::unique_ptr<GstPlayer>> players_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<GstPlayers> g_players = std::make_unique<GstPlayers>();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GstPlayerTextureController {
|
||||
static const MethodChannel _channel = MethodChannel('flutter_gstreamer_player');
|
||||
|
||||
late int textureId;
|
||||
static int _id = 0;
|
||||
|
||||
Future<int> initialize(String pipeline) async {
|
||||
// No idea why, but you have to increase `_id` first before pass it to method channel,
|
||||
// if not, receiver of method channel always received 0
|
||||
GstPlayerTextureController._id = GstPlayerTextureController._id + 1;
|
||||
|
||||
textureId = await _channel.invokeMethod('PlayerRegisterTexture', {
|
||||
'pipeline': pipeline,
|
||||
'playerId': GstPlayerTextureController._id,
|
||||
});
|
||||
|
||||
return textureId;
|
||||
}
|
||||
|
||||
Future<Null> dispose() {
|
||||
return _channel.invokeMethod('dispose', {'textureId': textureId});
|
||||
}
|
||||
|
||||
bool get isInitialized => textureId != null;
|
||||
}
|
||||
|
||||
class GstPlayer extends StatefulWidget {
|
||||
String pipeline;
|
||||
|
||||
GstPlayer({Key? key, required this.pipeline}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<GstPlayer> createState() => _GstPlayerState();
|
||||
}
|
||||
|
||||
class _GstPlayerState extends State<GstPlayer> {
|
||||
final _controller = GstPlayerTextureController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
initializeController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(GstPlayer oldWidget) {
|
||||
if (widget.pipeline != oldWidget.pipeline) {
|
||||
initializeController();
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
Future<Null> initializeController() async {
|
||||
await _controller.initialize(
|
||||
widget.pipeline,
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: _controller.isInitialized
|
||||
? Texture(textureId: _controller.textureId)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
set(PROJECT_NAME "flutter_gstreamer_player")
|
||||
project(${PROJECT_NAME} LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
add_compile_options(-fPIC)
|
||||
|
||||
# This value is used when generating builds using this plugin, so it must
|
||||
# not be changed
|
||||
set(PLUGIN_NAME "flutter_gstreamer_player_plugin")
|
||||
|
||||
# GStreamer libraries
|
||||
find_package(PkgConfig)
|
||||
pkg_search_module(GLIB REQUIRED glib-2.0)
|
||||
pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0)
|
||||
pkg_check_modules(GST_APP REQUIRED gstreamer-app-1.0)
|
||||
pkg_check_modules(GST_VIDEO REQUIRED gstreamer-video-1.0)
|
||||
include_directories(${GSTREAMER_INCLUDE_DIRS} ${GLIB2_INCLUDE_DIRS})
|
||||
# Custom GStreamer player
|
||||
add_library(GST_PLAYER STATIC
|
||||
"../gst_player/gst_player.cc"
|
||||
)
|
||||
include_directories(GST_PLAYER STATIC
|
||||
"../gst_player"
|
||||
)
|
||||
|
||||
add_library(${PLUGIN_NAME} SHARED
|
||||
"flutter_gstreamer_player_plugin.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)
|
||||
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)
|
||||
target_link_libraries(${PLUGIN_NAME} PRIVATE ${GST_APP_LIBRARIES} ${GST_VIDEO_LIBRARIES})
|
||||
target_link_libraries(${PLUGIN_NAME} PRIVATE GST_PLAYER)
|
||||
|
||||
# List of absolute paths to libraries that should be bundled with the plugin
|
||||
set(flutter_gstreamer_player_bundled_libraries
|
||||
""
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,124 @@
|
||||
#include "gst_player.h"
|
||||
|
||||
#include "include/flutter_gstreamer_player/flutter_gstreamer_player_plugin.h"
|
||||
#include "include/flutter_gstreamer_player/flutter_gstreamer_player_video_outlet.h"
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#define FLUTTER_GSTREAMER_PLAYER_PLUGIN(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj), flutter_gstreamer_player_plugin_get_type(), \
|
||||
FlutterGstreamerPlayerPlugin))
|
||||
|
||||
struct _FlutterGstreamerPlayerPlugin {
|
||||
GObject parent_instance;
|
||||
FlMethodChannel* method_channel;
|
||||
FlTextureRegistrar* texture_registrar;
|
||||
};
|
||||
|
||||
std::unordered_map<int32_t, VideoOutlet*> g_video_outlets;
|
||||
|
||||
G_DEFINE_TYPE(FlutterGstreamerPlayerPlugin, flutter_gstreamer_player_plugin, g_object_get_type())
|
||||
|
||||
// Called when a method call is received from Flutter.
|
||||
static void flutter_gstreamer_player_plugin_handle_method_call(
|
||||
FlutterGstreamerPlayerPlugin* self,
|
||||
FlMethodCall* method_call) {
|
||||
g_autoptr(FlMethodResponse) response = nullptr;
|
||||
|
||||
const gchar* method = fl_method_call_get_name(method_call);
|
||||
|
||||
// TODO properly handle these method calls
|
||||
if (strcmp(method, "getPlatformVersion") == 0) {
|
||||
struct utsname uname_data = {};
|
||||
uname(&uname_data);
|
||||
g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version);
|
||||
g_autoptr(FlValue) result = fl_value_new_string(version);
|
||||
response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
|
||||
} else if (strcmp(method, "PlayerRegisterTexture") == 0) {
|
||||
auto arguments = fl_method_call_get_args(method_call);
|
||||
auto pipeline =
|
||||
fl_value_get_string(fl_value_lookup_string(arguments, "pipeline"));
|
||||
int32_t player_id =
|
||||
fl_value_get_int(fl_value_lookup_string(arguments, "playerId"));
|
||||
auto [it, added] = g_video_outlets.try_emplace(player_id, nullptr);
|
||||
|
||||
GstPlayer* gstPlayer = g_players->Get(player_id);
|
||||
|
||||
if (added) {
|
||||
it->second = video_outlet_new();
|
||||
|
||||
FL_PIXEL_BUFFER_TEXTURE_GET_CLASS(it->second)->copy_pixels =
|
||||
video_outlet_copy_pixels;
|
||||
fl_texture_registrar_register_texture(self->texture_registrar,
|
||||
FL_TEXTURE(it->second));
|
||||
auto video_outlet_private = (VideoOutletPrivate*) video_outlet_get_instance_private(it->second);
|
||||
video_outlet_private->texture_id = reinterpret_cast<int64_t>(FL_TEXTURE(it->second));
|
||||
|
||||
gstPlayer->onVideo([texture_registrar = self->texture_registrar,
|
||||
video_outlet_ptr = it->second,
|
||||
video_outlet_private = video_outlet_private]
|
||||
(uint8_t* frame, int32_t width, int32_t height) -> void {
|
||||
video_outlet_private->buffer = frame;
|
||||
video_outlet_private->video_width = width;
|
||||
video_outlet_private->video_height = height;
|
||||
fl_texture_registrar_mark_texture_frame_available(
|
||||
texture_registrar,
|
||||
FL_TEXTURE(video_outlet_ptr));
|
||||
});
|
||||
}
|
||||
|
||||
gstPlayer->play(pipeline);
|
||||
|
||||
response =
|
||||
FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_int(
|
||||
((VideoOutletPrivate*) video_outlet_get_instance_private(it->second))->texture_id
|
||||
)));
|
||||
} else if (strcmp(method, "dispose") == 0) {
|
||||
g_autoptr(FlValue) result = fl_value_new_bool(true);
|
||||
response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
|
||||
} else {
|
||||
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
|
||||
}
|
||||
|
||||
fl_method_call_respond(method_call, response, nullptr);
|
||||
}
|
||||
|
||||
static void flutter_gstreamer_player_plugin_dispose(GObject* object) {
|
||||
G_OBJECT_CLASS(flutter_gstreamer_player_plugin_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void flutter_gstreamer_player_plugin_class_init(FlutterGstreamerPlayerPluginClass* klass) {
|
||||
G_OBJECT_CLASS(klass)->dispose = flutter_gstreamer_player_plugin_dispose;
|
||||
}
|
||||
|
||||
static void flutter_gstreamer_player_plugin_init(FlutterGstreamerPlayerPlugin* self) {}
|
||||
|
||||
static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
|
||||
gpointer user_data) {
|
||||
FlutterGstreamerPlayerPlugin* plugin = FLUTTER_GSTREAMER_PLAYER_PLUGIN(user_data);
|
||||
flutter_gstreamer_player_plugin_handle_method_call(plugin, method_call);
|
||||
}
|
||||
|
||||
void flutter_gstreamer_player_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
|
||||
FlutterGstreamerPlayerPlugin* plugin = FLUTTER_GSTREAMER_PLAYER_PLUGIN(
|
||||
g_object_new(flutter_gstreamer_player_plugin_get_type(), nullptr));
|
||||
|
||||
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
|
||||
g_autoptr(FlMethodChannel) channel =
|
||||
fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
|
||||
"flutter_gstreamer_player",
|
||||
FL_METHOD_CODEC(codec));
|
||||
|
||||
plugin->texture_registrar =
|
||||
fl_plugin_registrar_get_texture_registrar(registrar);
|
||||
|
||||
fl_method_channel_set_method_call_handler(channel, method_call_cb,
|
||||
g_object_ref(plugin),
|
||||
g_object_unref);
|
||||
|
||||
g_object_unref(plugin);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef FLUTTER_PLUGIN_FLUTTER_GSTREAMER_PLAYER_PLUGIN_H_
|
||||
#define FLUTTER_PLUGIN_FLUTTER_GSTREAMER_PLAYER_PLUGIN_H_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#ifdef FLUTTER_PLUGIN_IMPL
|
||||
#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
#define FLUTTER_PLUGIN_EXPORT
|
||||
#endif
|
||||
|
||||
typedef struct _FlutterGstreamerPlayerPlugin FlutterGstreamerPlayerPlugin;
|
||||
typedef struct {
|
||||
GObjectClass parent_class;
|
||||
} FlutterGstreamerPlayerPluginClass;
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT GType flutter_gstreamer_player_plugin_get_type();
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void flutter_gstreamer_player_plugin_register_with_registrar(
|
||||
FlPluginRegistrar* registrar);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif // FLUTTER_PLUGIN_FLUTTER_GSTREAMER_PLAYER_PLUGIN_H_
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef VIDEO_OUTLET_H_
|
||||
#define VIDEO_OUTLET_H_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
struct _VideoOutletClass {
|
||||
FlPixelBufferTextureClass parent_class;
|
||||
};
|
||||
|
||||
struct VideoOutletPrivate {
|
||||
int64_t texture_id = 0;
|
||||
uint8_t* buffer = nullptr;
|
||||
int32_t video_width = 0;
|
||||
int32_t video_height = 0;
|
||||
};
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE(VideoOutlet, video_outlet, MY_OPENGL, VIDEO_OUTLET,
|
||||
FlPixelBufferTexture)
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE(VideoOutlet, video_outlet, fl_pixel_buffer_texture_get_type(), G_ADD_PRIVATE(VideoOutlet))
|
||||
|
||||
static VideoOutlet* video_outlet_new() {
|
||||
return MY_OPENGL_VIDEO_OUTLET(g_object_new(video_outlet_get_type(), nullptr));
|
||||
}
|
||||
|
||||
static gboolean video_outlet_copy_pixels(
|
||||
FlPixelBufferTexture* texture, const uint8_t** out_buffer, uint32_t* width,
|
||||
uint32_t* height, GError** error) {
|
||||
auto video_outlet_private = (VideoOutletPrivate*) video_outlet_get_instance_private(MY_OPENGL_VIDEO_OUTLET(texture));
|
||||
*out_buffer = video_outlet_private->buffer;
|
||||
*width = video_outlet_private->video_width;
|
||||
*height = video_outlet_private->video_height;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void video_outlet_class_init(VideoOutletClass* klass) {
|
||||
FL_PIXEL_BUFFER_TEXTURE_CLASS(klass)->copy_pixels = video_outlet_copy_pixels;
|
||||
}
|
||||
|
||||
static void video_outlet_init(VideoOutlet* self) {}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,62 @@
|
||||
name: flutter_gstreamer_player
|
||||
description: A new flutter plugin project.
|
||||
version: 0.0.1
|
||||
homepage:
|
||||
|
||||
environment:
|
||||
sdk: ">=2.16.2 <3.0.0"
|
||||
flutter: ">=2.5.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^1.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter.
|
||||
flutter:
|
||||
# This section identifies this Flutter project as a plugin project.
|
||||
# The 'pluginClass' and Android 'package' identifiers should not ordinarily
|
||||
# be modified. They are used by the tooling to maintain consistency when
|
||||
# adding or updating assets for this project.
|
||||
plugin:
|
||||
platforms:
|
||||
linux:
|
||||
pluginClass: FlutterGstreamerPlayerPlugin
|
||||
|
||||
# To add assets to your plugin package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||
|
||||
# To add custom fonts to your plugin package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/custom-fonts/#from-packages
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_gstreamer_player/flutter_gstreamer_player.dart';
|
||||
|
||||
void main() {
|
||||
const MethodChannel channel = MethodChannel('flutter_gstreamer_player');
|
||||
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUp(() {
|
||||
channel.setMockMethodCallHandler((MethodCall methodCall) async {
|
||||
return '42';
|
||||
});
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
channel.setMockMethodCallHandler(null);
|
||||
});
|
||||
|
||||
test('getPlatformVersion', () async {
|
||||
expect(await FlutterGstreamerPlayer.platformVersion, '42');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user