ryanheise / just_audio

Audio Player
1.05k stars 677 forks source link

Support Microsoft Windows ? #103

Open chuoichien opened 4 years ago

chuoichien commented 4 years ago

Sorry, is this plugin support microsoft windows ?

alexmercerind commented 3 years ago

Update... Playback started working... I was invoking a nullptr by mistake.

This is FFI based binding to libVLC. But still, if I can't have callbacks from other threads... how will I notify Dart about events.

ffi

Dart code

import 'dart:ffi' as ffi;
final dylib = ffi.DynamicLibrary.open('C:/Users/alexmercerind/Documents/application/cxx/main.dll');

typedef cxxInit = ffi.Void Function();
typedef dartInit = void Function();

dartInit init = dylib
.lookup<ffi.NativeFunction<cxxInit>>('init')
.asFunction();

typedef cxxOpen = ffi.Void Function();
typedef dartOpen = void Function();

dartInit open = dylib
.lookup<ffi.NativeFunction<cxxInit>>('open')
.asFunction();

typedef callback = ffi.Void Function();

typedef cxxOn = ffi.Void Function(ffi.Pointer<ffi.NativeFunction<callback>>);
typedef dartOn = void Function(ffi.Pointer<ffi.NativeFunction<callback>>);

dartOn on = dylib
.lookup<ffi.NativeFunction<cxxOn>>('on')
.asFunction();

void dartMethod() {
  print('Event!');
}

void main() async {
  init();
  open();
  await Future.delayed(Duration(days: 1));
}

They moved it from FFI 1.0 to 1.1 😑. No progress since early 2019. http://dartbug.com/37022. This "1.0" isn't really ready.

ryanheise commented 3 years ago

I think this is the same problem with platform channels isn't it? Dart is single-threaded, so the platform code must switch to the main thread before sending a message to Dart over method/event channels.

To do async callbacks you would need to have a separate isolate to receive those messages. Now in terms of platform channels there is some news on that front: https://github.com/flutter/flutter/issues/13937#issuecomment-784571153 .

ryanheise commented 3 years ago

See https://flutter.dev/docs/development/platform-integration/platform-channels#channels-and-platform-threading

ryanheise commented 3 years ago

Some more ideas: https://github.com/audiooffler/JucyFluttering/

alexmercerind commented 3 years ago

They are also doing async callbacks on the main thread using some "Native Ports".

I found this guy's minimal example to study https://github.com/mikeperri/flutter-native-callbacks-example.

Thanks for the repo you gave, let's see how they are calling Dart methods from C/C++ from other threads (in a way).

alexmercerind commented 3 years ago

@ryanheise @hacker1024

Finally! 🎉.

I'm able to receive data (not call-back, but it will work) from another thread. Using that "Native Ports" approach.

Even then, its very different approach. I had to extract things from already defined method in 'package:isolate/port.dart. The example repository I mentioned above (https://github.com/mikeperri/flutter-native-callbacks-example), used to just show a single callback, but I don't want a single callback. I wanted a stream like behaviour (callbacks that happen own their own, not single time when I call something from Dart).

He was using this method to get result from another thread.

return await singleResponseFuture((port) => nMethodA(port.nativePort));

But it returned only single value & then there was no further thing that we could do. So I went to the declaration of singleResponseFuture and saw this inside...

Future<R> singleResponseFuture<R>(void Function(SendPort responsePort) action,
    {Duration timeout, R timeoutValue}) {
  var completer = Completer<R>.sync();
  var responsePort = RawReceivePort();
  Timer timer;
  var zone = Zone.current;
  responsePort.handler = (Object response) {
    responsePort.close();
    timer?.cancel();
    zone.run(() {
      _castComplete<R>(completer, response);
    });
  };
  if (timeout != null) {
    timer = Timer(timeout, () {
      responsePort.close();
      completer.complete(timeoutValue);
    });
  }
  try {
    action(responsePort.sendPort);
  } catch (error, stack) {
    responsePort.close();
    timer?.cancel();
    // Delay completion because completer is sync.
    scheduleMicrotask(() {
      completer.completeError(error, stack);
    });
  }
  return completer.future;
}

Seemed pretty confusing, then I saw RawReceivePort. Turns out I can just instantiate it and and assign handler attribute to it, for just listening to the values that another thread will "yield". Now, I'm pretty confident to pull it off directly from FFI.

Now I just use this simple lines (along with other boilerplate) to recieve values from another thread.

RawReceivePort reciever = new RawReceivePort();
reciever.handler = (Object response) => print(response); // Printing values sent from C++ to console. (I can forward this to a StreamController).
nMethodA(reciever.sendPort.nativePort);

On the c++ part

__declspec(dllexport) void method_a(Dart_Port callbackPort) {
    std::thread t([=]() {

        for (int index = 0; index < 100; index++) {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            callbackToDartInt32(callbackPort, index);
        }
    });
    t.detach();
};

And on the console... (Getting int32_t from another thread)

D:\Music\app\bin>dart app.dart
0
1
2
3
4
5
6

This can't send advance structures like map, but who cares. Let's go.

ryanheise commented 3 years ago

It looks like flutter-native-callbacks-example and JucyFluttering have stumbled onto variants of the same sort of idea which involves going through receive ports, although they differ a bit in the details. The former depends on package:isolate while JucyFluttering just instantiates a ReceivePort directly.

Then your own evolution of the former example seems to make it look more similar to the latter example (i.e. JucyFluttering) except you are using RawReceiverPort instead of ReceiverPort.

Congrats on getting it to work :+1:

alexmercerind commented 3 years ago

Hey @ryanheise!

I've made my current work public here: dart_vlc I feel it is stable and feature-full enough for you to get started implementing Windows support for this project.

You can import the channels.dart from the plugin to build your implementation using that. You can see the other files for reference, I have added a lot of comments. You're a really good & dedicated maintainer.

I can submit a pull request, if you want me to add Windows support to your project using dart_vlc.

But, the best is to continue yourself, as I need time to get Linux version ready & add the remaining features as well. You're the developer of this project, so you know the best how you want the things to be.

I'm just an 18 year old doing all this C++/Dart myself, so I'm not sure how good my implementation is. Please let me know if something is wrong or you wanna get it changed.

I decided to not use FFI as of now, because it is just so basic. Most advance thing that it can send through NativePort is just array of string (I know that is enough to get it done, but it will take time & I'm sure that people are also waiting to get Windows support). I'll add Linux & Windows support & FFI version in future, so you don't have to worry after depending on the plugin.

Thanks! Finally this long issue thread on the repository will be closed.

ryanheise commented 3 years ago

Awesome! I think you've done a great job in making it reusable. This is definitely a nice approach that will help not only just_audio but the whole ecosystem, and I think wider adoption of your package should mean that it will end up receiving more open source contributions from a wider audience.

It also means that it should be easy enough for me to maintain a just_audio_vlc_windows package as part of this repo (as opposed to having a completely separate project and repo that I endorse).

alexmercerind commented 3 years ago

Why not just just_audio_windows instead of just_audio_vlc_windows? 😎

ryanheise commented 3 years ago

The federated plugin model allows for 3rd parties to create alternative implementations of the platform interface for the same platform, so they need to have different names. That is basically the purpose of our discussion above to establish a naming convention. The reason we may end up with multiple implementations of the same interface for the same platform is that different implementations may support different subsets of features, and may also come with different licenses, so depending on an app's requirements, they can choose which implementation they want to link into their app.

One example is that the iOS and macOS implementations are currently embedded within the main plugin but I am considering moving them out to separate, endorsed, packages, the same way that just_audio_web was done. The reason I might do this is because the current iOS/macOS implementation is based on AVQueuePlayer but I would like to create an AVAudioEngine-based implementation. It is possible in the process that we may gain some features and also lose some features, so a choice may be useful.

ryanheise commented 3 years ago

@ryanheise, so I was just saying why not name it just_audio_windows like just_audio_web.

Had I thought of this naming convention earlier, I may have named it as just_audio_html5_web since there are definitely alternative ways to implement the web platform that have different tradeoffs. For example, in the html5-based approach, a visualizer is impossible. In a web-audio-based approach a visualizer is possible, but it runs into CORS restrictions which the html5-based implementation doesn't have.

alexmercerind commented 3 years ago

I've also added Linux support now. You're so good to go now.

I'll also add things like player state etc. and being able to address playlist during playback itself. (But it is still not as powerful as your library, you've added a ton of stuff since I last used. Great work.)

alexmercerind commented 3 years ago

@ryanheise Are you planning to not use this way? 😶

ryanheise commented 3 years ago

@alexmercerind Sorry I've been a bit preoccupied this week dealing with a neck condition which has me a bit worried since the doctors say it's impacting on the nerves controlling my arm. Apparently I have disc damage and somehow neck arthritis, and the holes that my nerves go through have shrunk.

Assuming good health, I also don't want to raise expectations too high that I'll be able to implement this quickly even though I would be tempted to start working on this now. Actually, I have a long list of high priority issues, the highest of which is to get the next release of audio_service out (since many other things depend on that). So as quickly as a single person can do this, it will happen whenever I get up to it, but I am hoping contributors from the community will join in and contribute on tasks that I'm not currently working on, and with the amazing work you've done I expect it would not actually be too difficult to get the job done. Otherwise, see the above mention/link "Support Linux?" where I have commented that when I have time, Linux will probably be my first go at this task, since I personally have a Linux computer rather than a Windows one.

Anyway, sorry again for not updating in this thread what's been going on.

It may be good for me to have some way to broadcast to everyone what I'm currently working on, just so people can get a sense of how long it will take me to get up to a particular task in the todo list.

alexmercerind commented 3 years ago

@ryanheise ,

No no nevermind. No worries at all.

I'm really sorry to hear that. 🙁

Nothing comes before your own personal health, get well soon. 🙏🏻

I appreciate you shared what you're going through presently. I pray for you to get okay again. Have good rest.

Thankyou very much.

megamegax commented 3 years ago

I thought I try to help you out here since I have both linux and windows installed on my computer, so I tried to create a vlc implementation of just_audio using web plugin as an example and dartvlc as an underlying dependency: https://github.com/megamegax/just_audio I couldn't get it to work but maybe it helps when you get to it I don't know :D feel free to use up anything from it you can.

alexmercerind commented 3 years ago

@megamegax. Great job getting it ready. I'll improve the things in dart_vlc over time (just_audio is still more feature-full).

I'm not sure how federated plugin model goes & licensing etc. So, ryanheise knows the best, how it should be.

ryanheise commented 3 years ago

@megamegax Nice job! I really appreciate that you are willing to help make this project better, especially since I may need to lean more on this amazing open source community's support going forward. More than ever before due to my condition, I am so happy to see every time someone makes a contribution.

I hadn't been a fan of a discord server before, but now that it is easier to talk than to type, I wonder if I should create one just for contributor/developer chats on how we can approach building a certain feature, etc.

hacker1024 commented 3 years ago

Sorry I've been a bit preoccupied this week dealing with a neck condition which has me a bit worried since the doctors say it's impacting on the nerves controlling my arm. Apparently I have disc damage and somehow neck arthritis, and the holes that my nerves go through have shrunk.

I'm sorry to hear that. Get well soon!

I hadn't been a fan of a discord server before, but now that it is easier to talk than to type, I wonder if I should create one just for contributor/developer chats on how we can approach building a certain feature, etc.

What about GitHub discussions?

ryanheise commented 3 years ago

Thanks, I really appreciate it!

I hadn't been a fan of a discord server before, but now that it is easier to talk than to type, I wonder if I should create one just for contributor/developer chats on how we can approach building a certain feature, etc.

What about GitHub discussions?

I think GitHub discussions is for typing rather than talking (correct me if I'm wrong) not terribly different from issues, so a discord server may at least add another mode of communication. When it comes to (for example) bringing someone up to speed on the internals of a package, I suspect this mode of communication will probably be more efficient too, regardless of whether it helps prevent you from being locked into a single typing posture for too long.

PavelPZ commented 3 years ago

Congratulations on reaching Flutter Favorite. Thank you @ryanheise, @alexmercerind and @megamegax for your efforts in preparing the windows and linux version.

bdlukaa commented 3 years ago

Hello everyone! I have made some progress on the Windows version on @libwinmedia's fork (https://github.com/libwinmedia/just_audio).

All the essential features (play, pause, seek, volume, speed and playlist) are supported.

Here's the Feature list compared to the other platforms:

Feature Android iOS macOS Web Windows
read from URL
read from file
read from asset
read from byte stream (not tested)
request headers
DASH
HLS
ICY metadata
buffer status/position
play/pause/seek
set volume/speed
clip audio
playlists
looping/shuffling
compose audio
gapless playback
report player errors
handle phonecall interruptions
buffering/loading options
set pitch
skip silence
equalizer
volume boost

@alexmercerind, thanks for building libwinmedia, used on the windows version.

@ryanheise Do you think this would fit in a PR?

ryanheise commented 3 years ago

Awesome, @bdlukaa ! Would someone who has Windows like to test this?

In terms of a PR, the beauty of the federated plugin model is that you don't need my approval, you can hold just the windows implementation in a separate repository and publish it as a separate package so long as it conforms to the just_audio platform interface (and ideally follows some package naming conventions that we agreed on above -- just_audio_nativelib_platform.)

bdlukaa commented 3 years ago

@ryanheise good to hear!

I will publish and maintain the windows plugin, and whenever I do some changes I'll do a PR upgrading the just_audio_windows version. Sounds good?

ryanheise commented 3 years ago

Sounds good, just note the package name as mentioned above (since we will possibly have multiple different windows implementations based on different native libraries), and also if maintaining a separate package, your git repository only needs to contain the windows implementation since your pubspec.yaml will point to the hosted version of just_audio_platform_interface.

bdlukaa commented 3 years ago

Okay! I am thinking of adding the Linux (and UWP support in the future) support too, since libwinmedia supports Linux as well!

alexmercerind commented 3 years ago

@bdlukaa

Okay! I am thinking of adding the Linux (and UWP support in the future) support too, since libwinmedia supports Linux as well!

libwinmedia already supports Windows & Linux itself, and there is no big deal in it. Infact, I just wrapped WebKit GTK instance to play media (ON LINUX ONLY), so essentially its just a <audio> tag (which in turn uses GStreamer). I implemented playlists aswell, within C++ itself. Biggest thing you forgot to mention is that, libwinmedia only supports later versions of Windows 10 (v1703+). "UWP support", there is nothing UWP/win32 in bare audio playback, libwinmedia already uses modern UWP (WinRT) APIs.

@ryanheise

Would someone who has Windows like to test this?

libwinmedia is part of Harmonoid project & internal dependency for playback on Windows & Linux. So, everything is being used in Harmonoid app, thus tested.

Sounds good, just note the package name as mentioned above (since we will possibly have multiple different windows implementations based on different native libraries), and also if maintaining a separate package, your git repository only needs to contain the windows implementation since your pubspec.yaml will point to the hosted version of just_audio_platform_interface.

libwinmedia is mainly a C++ library, and I had to create a basic Flutter package (FFI bindings) to power Harmonoid. A simple package can be enough (with no native code) or just use existing libwinmedia.dart (which Harmonoid uses) pub.dev dependency as @bdlukaa did above. I have no platform channel interface. A lot of features are only implemented for Windows and not for Linux. Few things need testing, I can't do this alone. It's rather large thing to do. Would appreciate if community decides to expose more WinRT APIs.

audio_service

Apart from just_audio, libwinmedia is also capable of adding support to audio_service aswell, I exposed System Media Transport Control APIs aswell. Everything is well documented, examples are present on the repository. For Linux, there is already a MPRIS library.

So, I'll be glad if libwinmedia powers that.

Why libwinmedia ?

I was just tired of libVLC's large size & other options being either too weak or just very strictly licensed. Thus, I created my own C++ library. Linux implementation can be made to use GStreamer instead which will be a lot better than starting a WebKit webview.

Why not libwinmedia ?

You can yourself use C++/WinRT APIs (very much like C# to be honest, since it is a projection of those) instead of having another libwinmedia abstraction. libwinmedia's main goal was:

Features list

Most features mentioned in that list are already supported by WinRT APIs (and a lot more), its just that I didn't expose them all.

P.S. there really needs to be some discussions tab or discord to talk about just_audio or audio_service in general.

ryanheise commented 3 years ago

Windows support is now published with features and instructions listed in the README. All credit goes to @bdlukaa for the federated plugin implementation and @alexmercerind for libwinmedia. Awesome work!

I'll leave this issue open for a bit and so please post below if you encounter any serious issues. Once immediate issues are resolved, I'll close this issue and just_audio_libwinmedia can be used to report any future issues.

bdlukaa commented 3 years ago

I have tested it on my production app and it works as good as it should!

bdlukaa commented 3 years ago

I tried my best to implement all the features. Here's the features that can't be supported on Windows at all:

Here are the options that are supported by Windows, but aren't implemented yet:

Effects

Name Index Description
AcousticEchoCancellation 1 An acoustic echo cancellation effect.
AutomaticGainControl 3 A automatic gain control effect.
BassBoost 8 A bass boost effect.
BassManagement 13 A bass management effect.
BeamForming 4 A beam forming effect.
ConstantToneRemoval 5 A constant tone removal effect.
DynamicRangeCompression 17 A dynamic range compression effect.
EnvironmentalEffects 14 An environmental effect.
Equalizer 6 A equalizer effect.
FarFieldBeamForming 18 A far-field beam forming effect.
LoudnessEqualizer 7 A loudness equalizer effect.
NoiseSuppression 2 A noise suppression effect.
Other 0 Other.
RoomCorrection 12 A room correction effect.
SpeakerCompensation 16 A speaker compensation effect.
SpeakerFill 11 A speaker fill effect.
SpeakerProtection 15 A speaker protection effect.
VirtualHeadphones 10 A virtual headphones effect.
VirtualSurround 9 A virtual surround sound effect.
ryanheise commented 3 years ago

Nice, it looks like there's a lot of potential, and it's good to know that ClippingAudioSource can be supported which I think is the last core feature that is conditionally disabled from example_playlist.dart.

Some of those features you mentioned like caching and byte streams are currently handled in the Dart layer so shouldn't those work already? In both cases, just_audio sets up a proxy web server to implement the features, and then instead of getting the platform side of the plugin to play the original URL, it passes the proxy URL instead.

bdlukaa commented 3 years ago

just_audio sets up a proxy web server to implement the features

I didn't know that. So you're saying that byte streams should be working?

ryanheise commented 3 years ago

Yes, I would have expected this to work. One way to test it could be the caching example which uses a stream audio source under the hood, and that should activate the local proxy on all platforms except web.

bdlukaa commented 2 years ago

Hello everyone! As you all know, just_audio_libwinmedia was the option used to play audio on Windows, but there are several limitations when using libwinmedia.

While I was on my days off, I worked on a fully native implementation for windows that is working. Check it out on just_audio_windows. It isn't published to pub.dev because I'd like to know @ryanheise's opinion on endorsing the project.

If you'd like to try it, you can add it as a dependency to your project. No extra setups required:

dependencies:
  just_audio_windows:
    git:
      url: https://github.com/bdlukaa/just_audio.git
      path: just_audio_windows/

Note that this is an early version with only a few features (most important ones) implemented, but example/main.dart works as expected. Contributions are welcome!

ryanheise commented 2 years ago

I like the direction of this - I noticed it doesn't have a license yet, is that decided?

I'll just mention again that since I'm on Linux it is something I will need to rely on others to test.

From here, let's get some people trying out just_audio_windows. If it works for testers, let's publish it and get it advertised in the just_audio README where it will have more eyes to detect more bugs and help reach maturity.

Once it reaches maturity, we can start looking at the endorsement process which should include some quality assurance that is consistent with the Flutter Favorites programme. A key requirement I would have is to ensure that semantic versioning, as interpreted by pub.dev, is observed. This should guarantee that an update to just_audio_windows doesn't cause just_audio to break its own Flutter Favorite obligations via its transitive dependencies.

bdlukaa commented 2 years ago

@ryanheise cool! I'll be looking forward to implement all the missing features and fix bugs. I'll let you know when it gets in beta <3

bdlukaa commented 2 years ago

@ryanheise Hello! just_audio_windows is finally in beta. This means that the example/main.dart works completely. Playing, pausing, seeking, speed, volume and pitch are working properly. Also, you can use the play/pause button from the keyboard, and the app will update properly (thanks to the data event channel)

Next step is to impement playlists, documentated here.

just_audio_windows uses UWP's native MediaPlayer (winrt). This was chosen over the win32 because it's more recent. Also, Flutter deprecated support for Windows 7 and 8. UWP also works on X Box, targeting this device as well. UWP's MediaPlayer is a lot easier to use than the win32 one, with less boilerplate code.

LMK your toughts on endorsing the windows implementation once fully implementated.

In case anyone wants to try it, add the following to your pubspec.yaml;

dependencies:
  just_audio:
    git: https://github.com/bdlukaa/just_audio.git
ryanheise commented 2 years ago

Awesome!

Can people using Windows test this and let @bdlukaa know how it goes?

Of course I'd be happy to endorse it once it becomes stable, and in the meantime I should definitely update the README with instructions on how to use it by adding the explicit dependency.

jmshrv commented 2 years ago

This is great news, that only leaves us with Linux :)

For Linux, I've wanted to make a GStreamer package for a while but never got around to it, and I doubt it could be endorsed due to GStreamer's LGPL license (unless it doesn't really count since you wouldn't statically compile GStreamer?)

ryanheise commented 2 years ago

Actually I don't know if there really is a problem endorsing a federated plugin implementation for Linux licenced under the LGPL. An LGPL library does not require the package linking that library to itself become LGPL The issue with LGPL for Flutter apps has specifically been the dynamic linking clause which is incompatible with the iOS and Android app stores. However, when building for those platforms, the Linux implementation would not be linked anyway.

If I am wrong about that, it is no big hassle to simply provide instructions in the just_audio README for how to manually add an extra dependency for the Linux implementation. I actually prefer this over endorsement because it's really not too difficult an imposition on the app developer to add one line to their pubspec.yaml, and it also protects the main plugin should anything ever break in the future.

ryanheise commented 2 years ago

Hi @UnicornsOnLSD , have you seen the latest comments on #332 regarding GStreamer? Let's discuss over in that thread.

ryanheise commented 2 years ago

@bdlukaa Ii am updating the main README to point to this new Windows plugin. Since your plugin is now in beta, would you consider publishing the beta on pub.dev using appropriate semantic versioning for a beta prerelease?

bdlukaa commented 2 years ago

Sure! https://pub.dev/packages/just_audio_windows.

To use it, just add it to your dependencies:

dependencies:
  just_audio:
  just_audio_windows:
bdlukaa commented 1 year ago

Hi everyone! I'm glad to announce that just_audio_windows now has support for playlists (thanks to @chengyuhui - https://github.com/bdlukaa/just_audio_windows/pull/8) in the latest version. Check it out!

ryanheise commented 1 year ago

Awesome! @chengyuhui out of curiosity, is it gapless?

Regarding concatenatingMove, if it's gapless, the iOS implementation might be a good basis for inspiration, but if it's not gapless, the web implementation might be more similar. For gapless, the idea was to handle all playlist reorderings through a single function (enqueueFrom) which covers all cases including adding, removing and moving. enqueueFrom is then a central place to first compare the old order to the new order and see if the "current item" would still be the same. If it is the same, we try not to interrupt its playback so the sound continues coming out smoothly.

kmod-midori commented 1 year ago

Yes, based on https://learn.microsoft.com/en-us/windows/uwp/audio-video-camera/media-playback-with-mediasource#play-a-list-of-media-items-with-mediaplaybacklist.

I will try to look into the iOS implementation to see how this can be done, though I don't really speak Objective-C :)