diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1962bbcb..5b2ed891 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,10 @@ jobs: - run: flutter config --enable-windows-desktop - run: flutter pub get - run: flutter build windows --verbose + - uses: actions/upload-artifact@v1 + with: + name: dart_vlc_example + path: example/build/windows/runner/Release build_linux: name: Build Linux diff --git a/CHANGELOG.md b/CHANGELOG.md index 98e54bcb..92320040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.1.1 + +- Fixed setState being called after dispose (#75) (Finally) +- Improved memory management. +- Fixed ton of memory leaks. +- Fixed `Devices::all` & `Media::parse` causing crash on Windows. + ## 0.1.0 - Fixed build on Linux. diff --git a/README.md b/README.md index 64d137f7..2ebcaad7 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@

Flutter 🎞 media playback, broadcast, recording & chromecast library for Windows & Linux.

Written in C++ using libVLC & libVLC++.
-![](https://github.com/alexmercerind/dart_vlc/blob/assets/dart_vlc_6.png?raw=true) +![](https://github.com/alexmercerind/dart_vlc/blob/assets/dart_vlc_windows_11_1.PNG?raw=true) -![](https://github.com/alexmercerind/dart_vlc/blob/assets/dart_vlc_7.png?raw=true) +![](https://github.com/alexmercerind/dart_vlc/blob/assets/dart_vlc_windows_11_2.PNG?raw=true) ## Installation @@ -13,7 +13,7 @@ ```yaml dependencies: ... - dart_vlc: ^0.0.9 + dart_vlc: ^0.1.1 ``` **Dart CLI** @@ -21,11 +21,12 @@ dependencies: ```yaml dependencies: ... - dart_vlc_ffi: ^0.0.1 + dart_vlc_ffi: ^0.1.0 ``` More on Dart CLI implementation [here](./ffi/README.md). +Feel free to open issue, incase you find something to be not working. ## Support @@ -33,13 +34,6 @@ Consider supporting the project by starring the repository or buying me a coffee -**Donate in Crypto** - -- ETH (Ethereum) - - `0x92f92BC204cDFDAB655e6A69e5Aa4bA5476D7661` -- BTC (Bitcoin) - - `1M3DNwnX1GDauPoPr7VywojMRWygje8ctq` - Thanks a lot for your support. Looking for contributors for macOS port. @@ -328,7 +322,7 @@ You can see an example project [here](https://github.com/alexmercerind/dart_vlc/ Windows -![](https://github.com/alexmercerind/dart_vlc/blob/assets/dart_vlc_0.PNG?raw=true) +![](https://github.com/alexmercerind/dart_vlc/blob/assets/dart_vlc_6.png?raw=true) ## Workings diff --git a/dartvlc/broadcast.hpp b/dartvlc/broadcast.hpp index 556b2f61..5e5ccec8 100644 --- a/dartvlc/broadcast.hpp +++ b/dartvlc/broadcast.hpp @@ -71,8 +71,9 @@ class Broadcast { ); } - void dispose() { + ~Broadcast() { libvlc_vlm_release(this->instance.get()); + delete this->media; } private: @@ -89,6 +90,11 @@ class Broadcasts { return this->broadcasts[id]; } + void dispose(int id, std::function callback = []() -> void {}) { + delete this->broadcasts[id]; + callback(); + } + private: std::map broadcasts; }; diff --git a/dartvlc/chromecast.hpp b/dartvlc/chromecast.hpp index 87cc7ddd..3ad30998 100644 --- a/dartvlc/chromecast.hpp +++ b/dartvlc/chromecast.hpp @@ -48,8 +48,9 @@ class Chromecast { ); } - void dispose() { + ~Chromecast() { libvlc_vlm_release(this->instance.get()); + delete this->media; } private: @@ -66,6 +67,11 @@ class Chromecasts { return this->chromecasts[id]; } + void dispose(int id, std::function callback = []() -> void {}) { + delete this->chromecasts[id]; + callback(); + } + private: std::map chromecasts; }; diff --git a/dartvlc/device.hpp b/dartvlc/device.hpp index 05fd5258..35c2f29c 100644 --- a/dartvlc/device.hpp +++ b/dartvlc/device.hpp @@ -41,6 +41,9 @@ class Devices { std::vector all; void refresh() { + for (Device* device: this->all) { + delete device; + } this->all.clear(); VLC::Instance _ = VLC::Instance(0, nullptr); VLC::MediaPlayer __ = VLC::MediaPlayer(_); diff --git a/dartvlc/equalizer.hpp b/dartvlc/equalizer.hpp index 1ef31f6e..12bbd8af 100644 --- a/dartvlc/equalizer.hpp +++ b/dartvlc/equalizer.hpp @@ -102,6 +102,11 @@ class Equalizers { return id; } + void dispose(int id, std::function callback = []() -> void {}) { + delete this->equalizers[id]; + callback(); + } + private: std::map equalizers; }; diff --git a/dartvlc/internal/events.hpp b/dartvlc/internal/events.hpp index e0229ccc..efb2bc31 100644 --- a/dartvlc/internal/events.hpp +++ b/dartvlc/internal/events.hpp @@ -106,7 +106,7 @@ class PlayerEvents : public PlayerGetters { if (this->isPlaylistModified) { this->mediaListPlayer.setMediaList(this->mediaList); if (!this->mediaList.count()) { - this->state = new PlayerState(); + this->state->reset(); this->mediaListPlayer.stop(); return; } diff --git a/dartvlc/internal/setters.hpp b/dartvlc/internal/setters.hpp index 2827083f..996743b2 100644 --- a/dartvlc/internal/setters.hpp +++ b/dartvlc/internal/setters.hpp @@ -18,33 +18,34 @@ class PlayerSetters: public PlayerEvents { public: void open(MediaSource* mediaSource, bool autoStart = true) { - if (this->state->device == nullptr) - this->stop(); - this->state->medias = new Playlist({}, PlaylistMode::single); + /* Freed the previous `Media` objects when a new `Playlist` or `Media` is opened. */ + for (Media* media: this->state->medias->medias) { + delete media; + } + this->stop(); + this->state->medias->medias = {}; this->mediaList = VLC::MediaList(this->instance); if (mediaSource->mediaSourceType() == "MediaSourceType.media") { Media* media = dynamic_cast(mediaSource); VLC::Media _ = VLC::Media(this->instance, media->location, VLC::Media::FromLocation); this->mediaList.addMedia(_); this->mediaListPlayer.setMediaList(this->mediaList); - this->state->medias = new Playlist({ media }, PlaylistMode::single); + this->state->medias->medias = { media }; this->state->isPlaylist = false; - } else if (mediaSource->mediaSourceType() == "MediaSourceType.playlist") { Playlist* playlist = dynamic_cast(mediaSource); if (playlist->medias.empty()) return; - for (Media* _ : playlist->medias) { - VLC::Media media = VLC::Media(this->instance, _->location, VLC::Media::FromLocation); - this->mediaList.addMedia(media); + for (Media* media : playlist->medias) { + VLC::Media _ = VLC::Media(this->instance, media->location, VLC::Media::FromLocation); + this->mediaList.addMedia(_); } this->mediaListPlayer.setMediaList(this->mediaList); - this->state->medias = playlist; + this->state->medias->medias = playlist->medias; this->state->isPlaylist = true; } this->_onOpenCallback(this->mediaList.itemAtIndex(0)); - this->mediaListPlayer.playItemAtIndex(0); this->state->index = 0; this->state->isPlaying = this->mediaListPlayer.isPlaying(); @@ -123,7 +124,6 @@ class PlayerSetters: public PlayerEvents { void setEqualizer(Equalizer* equalizer) { this->mediaPlayer.setEqualizer(equalizer->equalizer); - this->state->equalizer = equalizer; } void setUserAgent(std::string userAgent) { @@ -173,7 +173,12 @@ class PlayerSetters: public PlayerEvents { } void move(int initial, int final) { - if (initial < 0 || initial >= this->state->medias->medias.size() || final < 0 || final >= this->state->medias->medias.size()) return; + if ( + initial < 0 || + initial >= this->state->medias->medias.size() || + final < 0 || + final >= this->state->medias->medias.size() + ) return; if (initial == final) return; this->isPlaylistModified = true; Media* _ = this->state->medias->medias[initial]; diff --git a/dartvlc/internal/state.hpp b/dartvlc/internal/state.hpp index 2bb490ac..4a993330 100644 --- a/dartvlc/internal/state.hpp +++ b/dartvlc/internal/state.hpp @@ -28,5 +28,29 @@ class PlayerState { float rate = 1.0; bool isPlaylist = false; Device* device = nullptr; + /* TODO: Not used yet. Equalizer* equalizer = nullptr; + */ + + void reset() { + this->index = 0; + this->medias->medias = {}; + this->isPlaying = false; + this->isValid = true; + this->isSeekable = true; + this->isCompleted = false; + this->position = 0; + this->duration = 0; + this->volume = 1.0; + this->rate = 1.0; + this->isPlaylist = false; + this->device = nullptr; + /* + this->equalizer = nullptr; + */ + } + + ~PlayerState() { + delete this->medias; + } }; diff --git a/dartvlc/mediasource/media.hpp b/dartvlc/mediasource/media.hpp index 455ac035..a1c95d60 100644 --- a/dartvlc/mediasource/media.hpp +++ b/dartvlc/mediasource/media.hpp @@ -23,6 +23,7 @@ VLC::Instance instance = VLC::Instance(0, nullptr); +/* Media object is cleared inside PlayerSetters::open. */ class Media: public MediaSource { public: int id; @@ -51,24 +52,26 @@ class Media: public MediaSource { return media; } - static Media* asset(int id, std::string path, bool parse = false, int timeout = 10000) { + static Media* directShow(int id, std::string resource) { Media* media = new Media(); media->id = id; - media->resource = path; - media->location = "file:///" + std::filesystem::temp_directory_path().u8string() + "/" + path; - media->mediaType = "MediaType.asset"; - if (parse) media->parse(timeout); + media->resource = resource; + media->location = resource; + media->mediaType = "MediaType.directShow"; return media; } - static Media* directShow(int id, std::string resource) { + /* Now done directly from dart_vlc. + static Media* asset(int id, std::string path, bool parse = false, int timeout = 10000) { Media* media = new Media(); media->id = id; - media->resource = resource; - media->location = resource; - media->mediaType = "MediaType.directShow"; + media->resource = path; + media->location = "file:///" + std::filesystem::temp_directory_path().u8string() + "/" + path; + media->mediaType = "MediaType.asset"; + if (parse) media->parse(timeout); return media; } + */ void parse(int timeout) { VLC::Media media = VLC::Media(instance, this->location, VLC::Media::FromLocation); @@ -113,14 +116,6 @@ class Media: public MediaSource { std::string mediaSourceType() { return "MediaSourceType.media"; } - - std::map get() { - std::map media; - media["id"] = this->id; - media["mediaType"] = this->mediaType; - media["resource"] = this->resource; - return media; - } }; diff --git a/dartvlc/mediasource/playlist.hpp b/dartvlc/mediasource/playlist.hpp index fa9d0f98..0748c0d2 100644 --- a/dartvlc/mediasource/playlist.hpp +++ b/dartvlc/mediasource/playlist.hpp @@ -35,15 +35,7 @@ class Playlist : public MediaSource { this->medias = medias; this->playlistMode = playlistMode; }; - - std::vector> get() { - std::vector> _medias; - for (Media* audio : this->medias) { - _medias.emplace_back(audio->get()); - } - return _medias; - } - + std::string mediaSourceType() { return "MediaSourceType.playlist"; } diff --git a/dartvlc/player.hpp b/dartvlc/player.hpp index 68c2617c..af972991 100644 --- a/dartvlc/player.hpp +++ b/dartvlc/player.hpp @@ -21,9 +21,10 @@ class Player: public PlayerSetters { this->instance = VLC::Instance(0, nullptr); } else { - char** args = new char*[commandlineArguments.size()]; - for (int index = 0; index < commandlineArguments.size(); index++) args[index] = commandlineArguments[index].data(); - this->instance = VLC::Instance(static_cast(commandlineArguments.size()), args); + this->argsSize = commandlineArguments.size(); + this->args = new char*[commandlineArguments.size()]; + for (int index = 0; index < commandlineArguments.size(); index++) this->args[index] = commandlineArguments[index].data(); + this->instance = VLC::Instance(static_cast(commandlineArguments.size()), this->args); } this->mediaPlayer = VLC::MediaPlayer(this->instance); this->mediaListPlayer = VLC::MediaListPlayer(this->instance); @@ -54,6 +55,18 @@ class Player: public PlayerSetters { void onException(std::function callback) { this->mediaPlayer.eventManager().onEncounteredError(callback); } + + ~Player() { + this->mediaPlayer.stop(); + delete this->state; + delete this->_videoFrameBuffer; + for (size_t i = 0; i < argsSize; i++) delete this->args[i]; + delete[] this->args; + } + +private: + char** args; + size_t argsSize; }; @@ -66,6 +79,11 @@ class Players { return this->players[id]; } + void dispose(int id, std::function callback = []() -> void {}) { + delete this->players[id]; + callback(); + } + private: std::map players; }; diff --git a/dartvlc/record.hpp b/dartvlc/record.hpp index e476a819..662e70b2 100644 --- a/dartvlc/record.hpp +++ b/dartvlc/record.hpp @@ -48,8 +48,9 @@ class Record { ); } - void dispose() { + ~Record() { libvlc_vlm_release(this->instance.get()); + delete this->media; } private: @@ -66,6 +67,11 @@ class Records { return this->records[id]; } + void dispose(int id, std::function callback = []() -> void {}) { + delete this->records[id]; + callback(); + } + private: std::map records; }; diff --git a/example/lib/main.dart b/example/lib/main.dart index 54f7484f..29226e26 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -63,6 +63,8 @@ class DartVLCExampleState extends State { @override Widget build(BuildContext context) { return MaterialApp( + themeMode: ThemeMode.dark, + darkTheme: ThemeData.dark(), home: Scaffold( appBar: AppBar( title: const Text('dart_vlc'), @@ -179,7 +181,7 @@ class DartVLCExampleState extends State { MediaType.file) { this.medias.add( Media.file(new File( - controller.text)), + controller.text.replaceAll('"', ''))), ); } else if (this.mediaType == MediaType.network) { diff --git a/example/pubspec.lock b/example/pubspec.lock index 028ec22b..8e3959d7 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -35,14 +35,14 @@ packages: path: ".." relative: true source: path - version: "0.0.9" + version: "0.1.1" dart_vlc_ffi: dependency: transitive description: name: dart_vlc_ffi url: "https://pub.dartlang.org" source: hosted - version: "0.0.7" + version: "0.0.9" ffi: dependency: transitive description: diff --git a/example/windows/CMakeLists.txt b/example/windows/CMakeLists.txt index 795aee5b..405e2b82 100644 --- a/example/windows/CMakeLists.txt +++ b/example/windows/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.15) -project(flutter_vlc_example LANGUAGES CXX) +project(dart_vlc_example LANGUAGES CXX) -set(BINARY_NAME "flutter_vlc_example") +set(BINARY_NAME "dart_vlc_example") cmake_policy(SET CMP0063 NEW) diff --git a/example/windows/runner/CMakeLists.txt b/example/windows/runner/CMakeLists.txt index 977e38b5..0b899a0b 100644 --- a/example/windows/runner/CMakeLists.txt +++ b/example/windows/runner/CMakeLists.txt @@ -4,7 +4,6 @@ project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" - "run_loop.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" diff --git a/example/windows/runner/flutter_window.cpp b/example/windows/runner/flutter_window.cpp index c4227230..27bef02c 100644 --- a/example/windows/runner/flutter_window.cpp +++ b/example/windows/runner/flutter_window.cpp @@ -4,9 +4,8 @@ #include "flutter/generated_plugin_registrant.h" -FlutterWindow::FlutterWindow(RunLoop* run_loop, - const flutter::DartProject& project) - : run_loop_(run_loop), project_(project) {} +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} FlutterWindow::~FlutterWindow() {} @@ -26,14 +25,12 @@ bool FlutterWindow::OnCreate() { return false; } RegisterPlugins(flutter_controller_->engine()); - run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { - run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); flutter_controller_ = nullptr; } diff --git a/example/windows/runner/flutter_window.h b/example/windows/runner/flutter_window.h index b663ddd5..4d184577 100644 --- a/example/windows/runner/flutter_window.h +++ b/example/windows/runner/flutter_window.h @@ -6,7 +6,6 @@ #include -#include "run_loop.h" #include "win32_window.h" // A window that does nothing but host a Flutter view. @@ -14,8 +13,7 @@ class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow driven by the |run_loop|, hosting a // Flutter view running |project|. - explicit FlutterWindow(RunLoop* run_loop, - const flutter::DartProject& project); + explicit FlutterWindow(const flutter::DartProject& project); virtual ~FlutterWindow(); protected: @@ -26,9 +24,6 @@ class FlutterWindow : public Win32Window { LPARAM const lparam) noexcept override; private: - // The run loop driving events for this window. - RunLoop* run_loop_; - // The project to run. flutter::DartProject project_; diff --git a/example/windows/runner/main.cpp b/example/windows/runner/main.cpp index 1d9da865..e89d654c 100644 --- a/example/windows/runner/main.cpp +++ b/example/windows/runner/main.cpp @@ -3,40 +3,29 @@ #include #include "flutter_window.h" -#include "run_loop.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t *command_line, _In_ int show_command) { - // Attach to console when present (e.g., 'flutter run') or create a - // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } - - // Initialize COM, so that it is available for use in the library and/or - // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - - RunLoop run_loop; - flutter::DartProject project(L"data"); - - std::vector command_line_arguments = - GetCommandLineArguments(); - + std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); - - FlutterWindow window(&run_loop, project); + FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"dart_vlc_example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); - - run_loop.Run(); - + MSG msg; + while (GetMessage(&msg, nullptr, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } ::CoUninitialize(); return EXIT_SUCCESS; } diff --git a/example/windows/runner/run_loop.cpp b/example/windows/runner/run_loop.cpp deleted file mode 100644 index f23c5f21..00000000 --- a/example/windows/runner/run_loop.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "run_loop.h" - -#include - -#include - -RunLoop::RunLoop() {} - -RunLoop::~RunLoop() {} - -void RunLoop::Run() { - MSG msg; - while (GetMessage(&msg, nullptr, 0, 0)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } -} - -void RunLoop::RegisterFlutterInstance( - flutter::FlutterEngine* flutter_instance) { - flutter_instances_.insert(flutter_instance); -} - -void RunLoop::UnregisterFlutterInstance( - flutter::FlutterEngine* flutter_instance) { - flutter_instances_.erase(flutter_instance); -} - -RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { - TimePoint next_event_time = TimePoint::max(); - for (auto instance : flutter_instances_) { - std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); - if (wait_duration != std::chrono::nanoseconds::max()) { - next_event_time = - std::min(next_event_time, TimePoint::clock::now() + wait_duration); - } - } - return next_event_time; -} diff --git a/example/windows/runner/run_loop.h b/example/windows/runner/run_loop.h deleted file mode 100644 index 000d3624..00000000 --- a/example/windows/runner/run_loop.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef RUNNER_RUN_LOOP_H_ -#define RUNNER_RUN_LOOP_H_ - -#include - -#include -#include - -// A runloop that will service events for Flutter instances as well -// as native messages. -class RunLoop { - public: - RunLoop(); - ~RunLoop(); - - // Prevent copying - RunLoop(RunLoop const&) = delete; - RunLoop& operator=(RunLoop const&) = delete; - - // Runs the run loop until the application quits. - void Run(); - - // Registers the given Flutter instance for event servicing. - void RegisterFlutterInstance( - flutter::FlutterEngine* flutter_instance); - - // Unregisters the given Flutter instance from event servicing. - void UnregisterFlutterInstance( - flutter::FlutterEngine* flutter_instance); - - private: - using TimePoint = std::chrono::steady_clock::time_point; - - // Processes all currently pending messages for registered Flutter instances. - TimePoint ProcessFlutterMessages(); - - std::set flutter_instances_; -}; - -#endif // RUNNER_RUN_LOOP_H_ diff --git a/ffi/CHANGELOG.md b/ffi/CHANGELOG.md index d3a30f0f..f3676583 100644 --- a/ffi/CHANGELOG.md +++ b/ffi/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.1.0 + +- Added new native methods for handling with memory leaks. + +## 0.0.9 + +- Fixed `Devices.all` & `Media.parse` causing crash on Windows. + ## 0.0.8 - Fixed build warnings on Linux. diff --git a/ffi/lib/src/device.dart b/ffi/lib/src/device.dart index 5c781f55..d7ad34a9 100644 --- a/ffi/lib/src/device.dart +++ b/ffi/lib/src/device.dart @@ -24,7 +24,7 @@ class Devices { devices.add(Device(devicesPtr.elementAt(i).value.toDartString(), devicesPtr.elementAt(i + 1).value.toDartString())); } - calloc.free(devicesPtr); + CleanupFFI.devices(); return devices; } } diff --git a/ffi/lib/src/equalizer.dart b/ffi/lib/src/equalizer.dart index 583e8d39..ab3d3a7f 100644 --- a/ffi/lib/src/equalizer.dart +++ b/ffi/lib/src/equalizer.dart @@ -51,6 +51,7 @@ class Equalizer { double.parse(_equalizer.elementAt(10).value.toDartString()): double.parse(_equalizer.elementAt(11).value.toDartString()), }; + CleanupFFI.equalizer(); return equalizer; } @@ -74,6 +75,7 @@ class Equalizer { double.parse(_equalizer.elementAt(10).value.toDartString()): double.parse(_equalizer.elementAt(11).value.toDartString()), }; + CleanupFFI.equalizer(); return equalizer; } diff --git a/ffi/lib/src/internal/ffi.dart b/ffi/lib/src/internal/ffi.dart index 251eba9a..2ff7b8dc 100644 --- a/ffi/lib/src/internal/ffi.dart +++ b/ffi/lib/src/internal/ffi.dart @@ -22,6 +22,10 @@ abstract class PlayerFFI { .lookup>('Player_create') .asFunction(); + static final PlayerDisposeDart dispose = dynamicLibrary + .lookup>('Player_dispose') + .asFunction(); + static final PlayerOpenDart open = dynamicLibrary .lookup>('Player_open') .asFunction(); @@ -172,6 +176,23 @@ abstract class EqualizerFFI { .asFunction(); } + +abstract class CleanupFFI { + + static final MediaClearDart media = dynamicLibrary + .lookup>('Media_clear') + .asFunction(); + + static final DevicesClearDart devices = dynamicLibrary + .lookup>('Devices_clear') + .asFunction(); + + static final EqualizerClearDart equalizer = dynamicLibrary + .lookup>('Equalizer_clear') + .asFunction(); +} + + bool isInitialized = false; void Function(int playerId, Uint8List frame) videoFrameCallback = (_, __) {}; final ReceivePort receiver = new ReceivePort() diff --git a/ffi/lib/src/internal/typedefs/devices.dart b/ffi/lib/src/internal/typedefs/devices.dart index 6029c1b3..81b89a84 100644 --- a/ffi/lib/src/internal/typedefs/devices.dart +++ b/ffi/lib/src/internal/typedefs/devices.dart @@ -5,3 +5,7 @@ import 'package:ffi/ffi.dart'; /// Devices::all typedef DevicesAllCXX = Pointer> Function(); typedef DevicesAllDart = Pointer> Function(); + +/// Following typedef is used for cleaning memory used by char**. +typedef DevicesClearCXX = Void Function(); +typedef DevicesClearDart = void Function(); diff --git a/ffi/lib/src/internal/typedefs/equalizer.dart b/ffi/lib/src/internal/typedefs/equalizer.dart index e71d45e8..24c7c15f 100644 --- a/ffi/lib/src/internal/typedefs/equalizer.dart +++ b/ffi/lib/src/internal/typedefs/equalizer.dart @@ -21,3 +21,7 @@ typedef EqualizerSetBandAmpDart = void Function( /// Equalizer::setPreAmp typedef EqualizerSetPreAmpCXX = Void Function(Int32 id, Float amp); typedef EqualizerSetPreAmpDart = void Function(int id, double amp); + +/// Following typedef is used for cleaning memory used by char**. +typedef EqualizerClearCXX = Void Function(); +typedef EqualizerClearDart = void Function(); diff --git a/ffi/lib/src/internal/typedefs/media.dart b/ffi/lib/src/internal/typedefs/media.dart index caedc8ff..5c9f5ad7 100644 --- a/ffi/lib/src/internal/typedefs/media.dart +++ b/ffi/lib/src/internal/typedefs/media.dart @@ -7,3 +7,7 @@ typedef MediaParseCXX = Pointer> Function( Pointer type, Pointer resource, Int32 timeout); typedef MediaParseDart = Pointer> Function( Pointer type, Pointer resource, int timeout); + +/// Following typedef is used for cleaning memory used by char**. +typedef MediaClearCXX = Void Function(); +typedef MediaClearDart = void Function(); diff --git a/ffi/lib/src/internal/typedefs/player.dart b/ffi/lib/src/internal/typedefs/player.dart index ca71bb94..94922e89 100644 --- a/ffi/lib/src/internal/typedefs/player.dart +++ b/ffi/lib/src/internal/typedefs/player.dart @@ -16,6 +16,12 @@ typedef PlayerCreateDart = void Function( int commandlineArgumentsCount, Pointer> commandlineArguments); +/// Following typedef is used for: +/// Player::~Player +typedef PlayerDisposeCXX = Void Function(Int32 id); +typedef PlayerDisposeDart = void Function(int id); + + /// Following typedef is used for: /// Player::open typedef PlayerOpenCXX = Void Function( diff --git a/ffi/lib/src/mediaSource/media.dart b/ffi/lib/src/mediaSource/media.dart index 6491706c..13248ae4 100644 --- a/ffi/lib/src/mediaSource/media.dart +++ b/ffi/lib/src/mediaSource/media.dart @@ -137,6 +137,6 @@ class Media extends MediaSource { this.metas['trackNumber'] = metas.elementAt(21).value.toDartString(); this.metas['trackTotal'] = metas.elementAt(22).value.toDartString(); this.metas['url'] = metas.elementAt(23).value.toDartString(); - calloc.free(metas); + CleanupFFI.media(); } } diff --git a/ffi/lib/src/player.dart b/ffi/lib/src/player.dart index 6ae9f6c2..7cb12330 100644 --- a/ffi/lib/src/player.dart +++ b/ffi/lib/src/player.dart @@ -272,6 +272,7 @@ class Player { this.positionController.close(); this.playbackController.close(); this.generalController.close(); + PlayerFFI.dispose(this.id); } /// Internally used [StreamController]s, diff --git a/ffi/native/dart_vlc.cpp b/ffi/native/dart_vlc.cpp index c53bdad0..0ea447ef 100644 --- a/ffi/native/dart_vlc.cpp +++ b/ffi/native/dart_vlc.cpp @@ -67,11 +67,16 @@ EXPORT void Player_create(int id, int videoWidth, int videoHeight, int commandLi } } +EXPORT void Player_dispose(int id) { + players->dispose(id); +} + EXPORT void Player_open(int id, bool autoStart, const char** source, int sourceSize) { std::vector medias; Player* player = players->get(id); for (int index = 0; index < 2 * sourceSize; index += 2) { Media* media; + /* Freed inside PlayerSetters::open */ const char* type = source[index]; const char* resource = source[index + 1]; if (strcmp(type, "MediaType.file") == 0) @@ -171,6 +176,7 @@ EXPORT void Player_setPlaylistMode(int id, const char* mode) { EXPORT void Player_add(int id, const char* type, const char* resource) { Player* player = players->get(id); Media* media; + /* Freed inside PlayerSetters::open */ if (strcmp(type, "MediaType.file") == 0) media = Media::file(0, resource, false); else if (strcmp(type, "MediaType.network") == 0) @@ -202,6 +208,9 @@ EXPORT void Player_move(int id, int initialIndex, int finalIndex) { player->move(initialIndex, finalIndex); } +char** _metasPointer = nullptr; +size_t _metasSize = 0; + EXPORT char** Media_parse(const char* type, const char* resource, int timeout) { Media* media; if (strcmp(type, "MediaType.file") == 0) @@ -210,17 +219,32 @@ EXPORT char** Media_parse(const char* type, const char* resource, int timeout) { media = Media::network(0, resource, true); else media = Media::directShow(0, resource); - char** metas = new char*[media->metas.size()]; + _metasPointer = new char*[media->metas.size()]; + _metasSize = media->metas.size(); int index = 0; for (auto &meta: media->metas) { - metas[index] = meta.second.data(); + _metasPointer[index] = new char[200]; + strncpy(_metasPointer[index], meta.second.data(), 200); index++; } - return metas; + delete media; + return _metasPointer; +} + +EXPORT void Media_clear() { + if (_metasPointer != nullptr) { + for (size_t i = 0; i < _metasSize; i++) { + delete _metasPointer[i]; + } + delete[] _metasPointer; + _metasPointer = nullptr; + _metasSize = 0; + } } EXPORT void Broadcast_create(int id, const char* type, const char* resource, const char* access, const char* mux, const char* dst, const char* vcodec, int vb, const char* acodec, int ab) { Media* media; + /* Freed inside ~Broadcast (Broadcasts::dispose) */ if (strcmp(type, "MediaType.file") == 0) media = Media::file(0, resource, false); else if (strcmp(type, "MediaType.network") == 0) @@ -245,12 +269,12 @@ EXPORT void Broadcast_start(int id) { } EXPORT void Broadcast_dispose(int id) { - Broadcast* broadcast = broadcasts->get(id, nullptr, nullptr); - broadcast->dispose(); + broadcasts->dispose(id); } EXPORT void Chromecast_create(int id, const char* type, const char* resource, const char* ipAddress) { Media* media; + /* Freed inside ~Chromecast (Chromecasts::dispose) */ if (strcmp(type, "MediaType.file") == 0) media = Media::file(0, resource, false); else if (strcmp(type, "MediaType.network") == 0) @@ -266,12 +290,12 @@ EXPORT void Chromecast_start(int id) { } EXPORT void Chromecast_dispose(int id) { - Chromecast* chromecast = chromecasts->get(id, nullptr, nullptr); - chromecast->dispose(); + chromecasts->dispose(id); } EXPORT void Record_create(int id, const char* savingFile, const char* type, const char* resource) { Media* media; + /* Freed inside ~Record (Records::dispose) */ if (strcmp(type, "MediaType.file") == 0) media = Media::file(0, resource, false); else if (strcmp(type, "MediaType.network") == 0) @@ -286,42 +310,61 @@ EXPORT void Record_start(int id) { } EXPORT void Record_dispose(int id) { - records->get(id, nullptr, "")->dispose(); + records->dispose(id); } +char** _devicesPointer = nullptr; +size_t _devicesSize = 0; + EXPORT char** Devices_all() { devices->refresh(); - char** _devices = new char*[(devices->all.size() * 2) + 1]; - _devices[0] = new char[200]; - strncpy(_devices[0], std::to_string(devices->all.size()).data(), 200); + _devicesPointer = new char*[(devices->all.size() * 2) + 1]; + _devicesSize = (devices->all.size() * 2) + 1; + _devicesPointer[0] = new char[200]; + strncpy(_devicesPointer[0], std::to_string(devices->all.size()).data(), 200); int index = 0; for (Device* device: devices->all) { - _devices[index + 1] = new char[200]; - strncpy(_devices[index + 1], device->id.data(), 200); - _devices[index + 2] = new char[200]; - strncpy(_devices[index + 2], device->name.data(), 200); + _devicesPointer[index + 1] = new char[200]; + strncpy(_devicesPointer[index + 1], device->id.data(), 200); + _devicesPointer[index + 2] = new char[200]; + strncpy(_devicesPointer[index + 2], device->name.data(), 200); index += 2; } - return _devices; + return _devicesPointer; } +EXPORT void Devices_clear() { + if (_devicesPointer != nullptr) { + for (size_t i = 0; i < _devicesSize; i++) { + delete _devicesPointer[i]; + } + delete[] _devicesPointer; + _devicesPointer = nullptr; + _devicesSize = 0; + } +} + +char** _equalizerPointer = nullptr; +size_t _equalizerSize = 0; + EXPORT char** Equalizer_createEmpty() { int id = equalizers->createEmpty(); Equalizer* equalizer = equalizers->get(id); - char** _equalizer = new char*[2 * equalizer->bandAmps.size() + 2]; - _equalizer[0] = new char[200]; - strncpy(_equalizer[0], std::to_string(id).data(), 200); - _equalizer[1] = new char[200]; - strncpy(_equalizer[1], std::to_string(equalizer->preAmp).data(), 200); + _equalizerPointer = new char*[2 * equalizer->bandAmps.size() + 2]; + _equalizerSize = 2 * equalizer->bandAmps.size() + 2; + _equalizerPointer[0] = new char[200]; + strncpy(_equalizerPointer[0], std::to_string(id).data(), 200); + _equalizerPointer[1] = new char[200]; + strncpy(_equalizerPointer[1], std::to_string(equalizer->preAmp).data(), 200); int index = 0; for (const auto&[band, amp]: equalizer->bandAmps) { - _equalizer[index + 2] = new char[200]; - strncpy(_equalizer[index + 2], std::to_string(band).data(), 200); - _equalizer[index + 3] = new char[200]; - strncpy(_equalizer[index + 3], std::to_string(amp).data(), 200); + _equalizerPointer[index + 2] = new char[200]; + strncpy(_equalizerPointer[index + 2], std::to_string(band).data(), 200); + _equalizerPointer[index + 3] = new char[200]; + strncpy(_equalizerPointer[index + 3], std::to_string(amp).data(), 200); index += 2; } - return _equalizer; + return _equalizerPointer; } EXPORT char** Equalizer_createMode(int mode) { @@ -329,20 +372,32 @@ EXPORT char** Equalizer_createMode(int mode) { static_cast(mode) ); Equalizer* equalizer = equalizers->get(id); - char** _equalizer = new char*[2 * equalizer->bandAmps.size() + 2]; - _equalizer[0] = new char[200]; - strncpy(_equalizer[0], std::to_string(id).data(), 200); - _equalizer[1] = new char[200]; - strncpy(_equalizer[1], std::to_string(equalizer->preAmp).data(), 200); + _equalizerPointer = new char*[2 * equalizer->bandAmps.size() + 2]; + _equalizerSize = 2 * equalizer->bandAmps.size() + 2; + _equalizerPointer[0] = new char[200]; + strncpy(_equalizerPointer[0], std::to_string(id).data(), 200); + _equalizerPointer[1] = new char[200]; + strncpy(_equalizerPointer[1], std::to_string(equalizer->preAmp).data(), 200); int index = 0; for (const auto&[band, amp]: equalizer->bandAmps) { - _equalizer[index + 2] = new char[200]; - strncpy(_equalizer[index + 2], std::to_string(band).data(), 200); - _equalizer[index + 3] = new char[200]; - strncpy(_equalizer[index + 3], std::to_string(amp).data(), 200); + _equalizerPointer[index + 2] = new char[200]; + strncpy(_equalizerPointer[index + 2], std::to_string(band).data(), 200); + _equalizerPointer[index + 3] = new char[200]; + strncpy(_equalizerPointer[index + 3], std::to_string(amp).data(), 200); index += 2; } - return _equalizer; + return _equalizerPointer; +} + +EXPORT void Equalizer_clear() { + if (_equalizerPointer != nullptr) { + for (size_t i = 0; i < _equalizerSize; i++) { + delete _equalizerPointer[i]; + } + delete[] _equalizerPointer; + _equalizerPointer = nullptr; + _equalizerSize = 0; + } } EXPORT void Equalizer_setBandAmp(int id, float band, float amp) { diff --git a/ffi/pubspec.yaml b/ffi/pubspec.yaml index 7fdf28db..65252101 100644 --- a/ffi/pubspec.yaml +++ b/ffi/pubspec.yaml @@ -1,6 +1,6 @@ name: dart_vlc_ffi description: FFI based binding to dart_vlc wrapper (Based on libVLC & libVLC++). -version: 0.0.8 +version: 0.1.0 homepage: https://github.com/alexmercerind/dart_vlc repository: https://github.com/alexmercerind/dart_vlc documentation: https://github.com/alexmercerind/dart_vlc/blob/master/README.md diff --git a/lib/src/widgets/video.dart b/lib/src/widgets/video.dart index f453d7af..276b97ee 100644 --- a/lib/src/widgets/video.dart +++ b/lib/src/widgets/video.dart @@ -172,9 +172,9 @@ class VideoState extends State