I have a service that listens to media buttons, but I only want to listen to those when my service is running since I have a button on the UI to start/stop the service.
How can I achieve the following two actions:
On MyService start-up, start receiving media button events to com.myApp/MyService.
On MyService end, stop receiving media button events in com.myApp/MyService.
The related logs are the following:
D MediaSessionService: Sending KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_HEADSETHOOK, scanCode=226, metaState=0, flags=0x8, repeatCount=0, eventTime=13258317, downTime=13258317, deviceId=4, source=0x101 } to com.myApp/MyService (userId=0)
Note:
I found out that I can start receiving media events after my app started to play audio. This is however not ideal, since I don't want to play audio in my app. (I am only using media events in order to trigger voice recognition)
After I played audio, my service is apparently the default media receiver. This means that when a media button is pressed, my service get instantiated, does nothing, and get destroyed. This is not an idea behavior for the end user since my app ends up 'stealing' these events that could potentially be handled by something else.
Overriding OnStartCommand() in my service does not help
Calling SetMediaButtonReceiver on the MediaSession does not help either
Using a BroadcastReceiver with the intent "android.intent.action.MEDIA_BUTTON" registered in the AudioManager does not work either
Relevant code (in C#, I am on Xamarin.Forms, but that should not have any impact on the way to achieve this)
public class MediaSessionCompatCallback : MediaSessionCompat.Callback
{
public Func<Intent, bool> MediaButtonEvent { get; set; }
public override bool OnMediaButtonEvent(Intent mediaButtonEvent) => MediaButtonEvent?.Invoke(mediaButtonEvent) ?? false;
}
[Service(Exported = true, Enabled = true)]
[IntentFilter(new[] { ServiceInterface })]
public class MediaBrowserService : MediaBrowserServiceCompat, AudioManager.IOnAudioFocusChangeListener
{
private MediaSessionCompat mediaSession;
private MediaSessionCompatCallback mediaSessionCallback;
private void CreateMediaSessionCallback()
{
mediaSessionCallback = new MediaSessionCompatCallback()
{
MediaButtonEvent = OnMediaButtonEvent
};
}
private void SetupMediaSession()
{
CreateMediaSessionCallback();
var stateBuilder = new PlaybackStateCompat.Builder().SetActions(PlaybackStateCompat.ActionPlay | PlaybackStateCompat.ActionPlayPause);
mediaSession = new MediaSessionCompat(this, nameof(MediaBrowserService));
mediaSession.SetFlags(MediaSessionCompat.FlagHandlesMediaButtons | MediaSessionCompat.FlagHandlesTransportControls);
mediaSession.SetPlaybackState(stateBuilder.Build());
mediaSession.SetCallback(mediaSessionCallback);
mediaSession.Active = true;
SessionToken = mediaSession.SessionToken;
}
private void BuildNotification() { [...] }
public override void OnCreate()
{
base.OnCreate();
SetupMediaSession();
StartForeground(135, BuildNotification());
ContextCompat.StartForegroundService(ApplicationContext, new Intent(ApplicationContext, Java.Lang.Class.FromType(typeof(MediaBrowserService))));
}
public override void OnDestroy()
{
base.OnDestroy();
if (mediaSession != null)
{
mediaSession.Active = false;
mediaSession.SetCallback(null);
mediaSession.Release();
mediaSession.Dispose();
mediaSession = null;
}
if (mediaSessionCallback != null)
{
mediaSessionCallback.Dispose();
mediaSessionCallback = null;
}
StopForeground(true);
StopSelf();
}
}
Related
I am trying to get a shoutcast URL (.stream) to stream audio in a cross-platform application. I've started with the Android app first and I cannot get the audio playing on the test device Samsung Galaxy S8.
However, the audio players work fine within the Emulator. If it weren't for the test device I would've assumed everything was working.
I've tried using "the local MediaPlayer" and "Plugin.MediaManager": Both work in the Emulator but none on the device. I have enabled the permissions required in the manifest: ACCESS_NETWORK_STATE, INTERNET, MEDIA_CONTENT_CONTROLS, RECORD_AUDIO, WAKE_LOCK, READ_EXTERNAL_STORAGE
(using MediaManager plugin)
in MainActivy:
protected override void OnCreate(Bundle savedInstanceState)
{
.....
CrossMediaManager.Current.Init(this);
.....
}
public class StreamingService: IStreaming.IStreaming
{
bool IsPrepared = false;
public async void Play()
{
await CrossMediaManager.Current.Play("http://someUrl/stream");
}
public void Pause()
{
CrossMediaManager.Current.Pause();
}
public void Stop()
{
CrossMediaManager.Current.Stop();
IsPrepared = false;
}
public int getResponse()
{
if (CrossMediaManager.Current.IsPlaying())
return 1;
else
return 0;
}
}
If I look at the data usage for the app on the device it is set to 0kb after a few tries it goes up by the kb currently at 3.24kb data usage. It doesnt appear that the media player is trying to access the stream, or can even access the stream.
I found a nuget package: LibVLCSharp.Forms
in the Main application created a class:
using LibVLCSharp.Shared;
public class RadioStream
{
readonly LibVLC _libVLC;
readonly MediaPlayer _mp;
public RadioStream()
{
if (DesignMode.IsDesignModeEnabled) return;
Core.Initialize();
_libVLC = new LibVLC();
_mp = new MediaPlayer(_libVLC);
}
public void Init()
{
_mp.Media = new Media(_libVLC, "http://url/stream", FromType.FromLocation);
_mp.Media.AddOption(":no-video");
}
public void Play(bool play)
{
if (play)
_mp.Play();
else _mp.Pause();
}
public bool isPlaying()
{
if (_mp.IsPlaying == false)
return false;
else
return true;
}
}
And its working!
In a while loop I check the isPlaying() that allows me to set the status of the stream and display accordingly.
Its simple at the moment, and stops playing when the internet state changes. But the above is working for simple playback.
I am currently developing an android xamarin app (android 6 and above) and I have got a problem.
Our customer wants to secure the app by a pinpad. Whenever the app is started the user has to enter a four digit pin.
We have created an activity for the pinpad. This works pretty fine, but the problem is the following:
The pinpad just opens if the app was completely killed (e.g. by the task manager ) -> cold started.
How can I achive that the pinpad opens if the app was in the background and reopend by the task manager for example (user pressed home button and then wants to start app again) -> warm started.
I've tried to do this by OnResume(), OnStart(),. But unfortunately they trigger every time an another activity (e.g. open detail view of list item) is opened.
use IActivityLifecycleCallbacks to listen the status.
Application registration ActivityLifecycleCallbacks, such, when each activity in the app lifecycle occurs, the Application can be listening to. The number of public void onActivityStarted(activity activity) and public void onActivityStopped(activity activity) of an activity can be used to determine whether the app is in the foreground. Because when the app is in the foreground, an activity must have started onActivityStarted but not onActivityStopped, so the statistics of the number of activities opened in the app must be 1. When the app switches to the background, activityStartCount will be 0.
so write a Helper classes :
public class AppFrontBackHelper
{
public static OnAppStatusListener mOnAppStatusListener;
private LifecycleCallBack lifecycleCallBack;
public AppFrontBackHelper()
{
}
/**
* Register status listener, only used in Application
* #param application
* #param listener
*/
public void register(Application application, OnAppStatusListener listener)
{
mOnAppStatusListener = listener;
lifecycleCallBack = new LifecycleCallBack();
application.RegisterActivityLifecycleCallbacks(lifecycleCallBack);
}
public void unRegister(Application application) => application.UnregisterActivityLifecycleCallbacks(lifecycleCallBack);
public interface OnAppStatusListener
{
void onFront();
void onBack();
}
public class LifecycleCallBack : Java.Lang.Object, Application.IActivityLifecycleCallbacks
{
public int activityStartCount { get; private set; }
public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
{
}
public void OnActivityDestroyed(Activity activity)
{
}
public void OnActivityPaused(Activity activity)
{
}
public void OnActivityResumed(Activity activity)
{
}
public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
{
}
public void OnActivityStarted(Activity activity)
{
activityStartCount++;
//A value from 1 to 0 indicates cutting from the background to the foreground
if (activityStartCount == 1)
{
if (mOnAppStatusListener != null)
{
mOnAppStatusListener.onFront();
}
}
}
public void OnActivityStopped(Activity activity)
{
activityStartCount--;
//A value from 1 to 0 indicates cutting from the foreground to the background
if (activityStartCount == 0)
{
//从前台切到后台
if (mOnAppStatusListener != null)
{
mOnAppStatusListener.onBack();
}
}
}
}
}
then custom an Application and regist the listener :
[Application]
public class MyApplication : Application,AppFrontBackHelper.OnAppStatusListener
{
protected MyApplication(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
public override void OnCreate()
{
base.OnCreate();
AppFrontBackHelper appFrontBackHelper = new AppFrontBackHelper();
appFrontBackHelper.register(this, this);
}
public void onBack()
{
Toast.MakeText(this, "from front to back", ToastLength.Short).Show();
}
public void onFront()
{
Toast.MakeText(this, "from back to front", ToastLength.Short).Show();
}
}
you could do something in the onFront() callback.
In case the network connectivity isn’t on to check if it is off, so, that I’m using to connectivity plug in.
I'm calling this code in ViewModelLocator class
Private static async void NetworkConnectivityChanged(object sender,Plugin.Connectivity.Abstractions.ConnectivityChangedEventArgs e){}
CrossConnectivity.Current.ConnectivityChanged = NetworkConnectivityChanged;
In my windows app after navigating if network connectivity status changed … Here this event is not fire but if not using navigation, we change the network status and it happens it's working.
The workaround is to implement the native network change handler on Winphone or UWP side and stop handling of network change on PCL side for just Winphone and UWP. You can do this by checking the platform before handling.
Create a new Network.cs class with the following code(This detects if there is any change in network connection)
public class InternetConnectionChangedEventArgs : EventArgs
{
public InternetConnectionChangedEventArgs(bool isConnected)
{
this.isConnected = isConnected;
}
public bool IsConnected
{
get { return this.isConnected; }
}
private bool isConnected;
}
public static class Network
{
public static event EventHandler<InternetConnectionChangedEventArgs>
InternetConnectionChanged;
static Network()
{
NetworkInformation.NetworkStatusChanged += (s) =>
{
if (InternetConnectionChanged != null)
{
var arg = new InternetConnectionChangedEventArgs(IsConnected);
InternetConnectionChanged(null, arg);
}
};
}
public static bool IsConnected
{
get
{
var profile = NetworkInformation.GetInternetConnectionProfile();
var isConnected = (profile != null
&& profile.GetNetworkConnectivityLevel() ==
NetworkConnectivityLevel.InternetAccess);
return isConnected;
}
}
}
Then in the app.xaml.cs in UWP or WinPhone register the network change handler in OnLaunched event like below
Network.InternetConnectionChanged += this.Network_InternetConnectionChanged;
and here is the event handler
private void Network_InternetConnectionChanged(object sender,InternetConnectionChangedEventArgs e)
{
if(e.IsConnected){
///code to handle when the internet connectivity is there
}
else{
//code to handle when the internet connectivity is lost
}
}
I'm trying to build a metronome application that has the capability to run in the background. As a starting point I decided to create something simple, a class that has a timer (the metronome itself), a class responsible for obtaining the MIDI output device and a class to play the sound. I'm having difficulty with how to make this run in the background. Additionally, another problem is the fact that the metronome needs to be executed when clicking an application button (in the main process).
Metronome Class:
public class Metronome
{
private DispatcherTimer timer = new DispatcherTimer();
private MidiDeviceSelector deviceSelector = new MidiDeviceSelector();
private void TimerStart()
{
timer.Start();
timer.Tick += timer_Tick;
}
private void timer_Tick(object sender, object e)
{
AudioPlayback.Beep1();
}
public void Start(int bpm)
{
double interval = (double)60.000f / (bpm);
timer.Interval = TimeSpan.FromSeconds(interval);
TimerStart();
}
public void Stop()
{
timer.Stop();
}
}
MidiDeviceSelector:
class MidiDeviceSelector
{
public MidiDeviceSelector()
{
GetOutputMidiDevice();
}
public async void GetOutputMidiDevice()
{
IMidiOutPort currentMidiOutputDevice;
DeviceInformation devInfo;
DeviceInformationCollection devInfoCollection;
string devInfoId;
devInfoCollection = await DeviceInformation.FindAllAsync(MidiOutPort.GetDeviceSelector());
if (devInfoCollection == null)
{
//notify the user that any device was found.
System.Diagnostics.Debug.WriteLine("Any device was found.");
}
devInfo = devInfoCollection[0];
if (devInfo == null)
{
//Notify the User that the device not found
System.Diagnostics.Debug.WriteLine("Device not found.");
}
devInfoId = devInfo.Id.ToString();
currentMidiOutputDevice = await MidiOutPort.FromIdAsync(devInfoId);
if (currentMidiOutputDevice == null)
{
//Notify the User that wasn't possible to create MidiOutputPort for the device.
System.Diagnostics.Debug.WriteLine("It was not possible to create the OutPort for the device.");
}
MidiDevice.midiDevice = currentMidiOutputDevice;
}
Class to Holds the MidiDevice:
class MidiDevice
{
public static IMidiOutPort midiDevice; //Bad practice i know.
}
Class to play the "toc" sound:
class AudioPlayback
{
static IMidiMessage beep1 = new MidiNoteOnMessage(9, 76, 90);
//static IMidiOutPort midiOutputDevice = (IMidiOutPort)MidiDeviceSelector.GetOutputMidiDevice();
public static void Beep1()
{
try
{
MidiDevice.midiDevice.SendMessage(beep1);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
}
Each class is contained in a different file. As you can see, it is a very simple code, if you see any bad programming practice, I apologise, I do not have much experience.
I was looking at the documentation, however, I did not succeed. How do I register an activity in the background and that there is interaction with the application's user interface (a button to stop and start the metronome).
My apologies for bad English, not my native language.
Thank you.
Two things you need to add to make this scenario work:
add the "backgroundMediaPlayback" capability as documented here: https://learn.microsoft.com/en-us/windows/uwp/audio-video-camera/background-audio
since you are using the MiDI APIs, you need to explicitly integrate with the SystemMediaTransportControls to prevent getting muted on minimize
I have update your repro sample and verified that it works correctly after adding those two things.
Sharing it here for your reference: https://1drv.ms/u/s!AovTwKUMywTNl9QJTeecnDzCf0WWyQ
I have "Handlers" able to trigger "Brokers" (an object that does something - not important here).
Handlers are listening to different kind of events:
TimeEvent: Every 10 seconds, 10 minutes (...)
FileSystemEvent: Once a file copied/moved/deleted
DbEvent: When a record is added to a DB table
MailEvent: When I received an email in my Office 365 mailbox
Each handler must have:
Start and stop methods (start/stop catching events)
An instance of the associated broker
A way to "trigger" the broker (Process method + specific set of arguments).
Each handler should
Trigger the associated broker when a specific event is raised
I want to trigger brokers from the base Handler class so I centralize my logic (fire events, catch exception, manage threads etc.). However, the base handler doesn't know how to call the broker (when to call this function, what parameters to send to the Process method) >> Only specialized children handlers know how to do that.
The only way I found is to implement in the base handler an Execute method accepting an Action parameter... I don't like this approach as it's not really straight forward (child needs to call base class otherwise nothing happens). I was hoping to find a better design to handle this. In addition I can tell you my developers will tell me they don't understand how to use the system.
abstract class Handler : IHandler
{
public IBroker Broker { get; protected set; }
public event ProcessedEventHandler Processed;
protected void OnProcessed(ProcessExecutionResult result) => Processed?.Invoke(this, result);
public static IHandler Create(IBroker broker)
{
if (broker is ITimerTriggeredBroker)
return new TimeHandler((ITimerTriggeredBroker)broker);
return null;
}
protected Handler(IBroker broker)
{
if (broker == null) throw new ArgumentNullException(nameof(broker));
Broker = broker;
}
public abstract void Start();
public abstract void Stop();
protected void Execute(Action action)
{
var res = new ProcessExecutionResult();
try
{
action?.Invoke();
res.IsSuccess = true;
}
catch (Exception ex)
{
res.Exception = ex;
res.IsSuccess = false;
}
finally
{
OnProcessed(res);
}
}
}
TimeHandler (handling Time related events)
class TimeHandler : Handler
{
private readonly Timer _timer;
private readonly DateTime _start;
private readonly TimeSpan _frequency;
public TimeHandler(ITimerTriggeredBroker broker)
: base(broker)
{
_start = broker.Trigger.StartTime;
_frequency = broker.Trigger.Frequency;
_timer = new Timer(_ => Execute(broker.Process));
}
(...)
}
FileHandler (handling FileSystem related events)
class FileHandler : Handler
{
private readonly FileSystemWatcher _watcher = new FileSystemWatcher();
public FileHandler(IFileTriggeredBroker broker)
: base(broker)
{
if (!Directory.Exists(broker.Trigger.DirectoryPath))
throw new DirectoryNotFoundException("Unable to locate the supplied directory");
_watcher.Filter = broker.Trigger.Filter;
_watcher.Path = broker.Trigger.DirectoryPath;
_watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.DirectoryName |
NotifyFilters.FileName;
_watcher.Created += (s, a) =>
{
if (IsCopied(a.FullPath)) Execute(() => broker.Process(a.FullPath));
};
}
There are several aspects to what you are trying to achieve:
The architecture should be easy to understand and follow by your programmers. It should guide them while they program and protect them from making errors.
It should be robust. For example, you should guarantee that every file created in the watched folder is processed.
In my answer I will ignore the robustness aspect. Please look at this seriously. FileSystemWatcher is not guaranteed to deliver all files created. Also, it is advisable to you separate the processing of FileSystemWatcher events in a separate thread or use .NET tasks for this.
Furthermore, I think you should consider using a queue, like Microsoft MQ, Azure Queue, RabbitMQ. You can do this directly or use a system like MassTransit.
Below I propose an architecture that will make it easier for your programmers to build upon.
General description
Divide the application in folders or different assemblies to make a clear separation between framework and specific handlers/brokers.
For each type of processing we create a specific message class and let the handler and broker implement a generic interface specific for the message type.
We will make use of the C# advanced type system to make sure that it is difficult to make mistakes and that the compiler will help the programmers to use the right things. For this we use generic interfaces and classes based on the message type class.
Main program
Here we will setup a manager that will register all handlers and brokers with their respective messages. This is a stand alone example, I suggest you use a system for dependency injection like for example AutoFac to further optimize this.
static void Main(string[] args)
{
var manager = new Manager();
manager.Register<FileHandlerMessage>(new FileHandler(), new FileBroker());
manager.Register<TimeHandlerMessage>(new TimeHandler(), new TimeBroker());
manager.Start();
Console.ReadLine();
manager.Stop();
}
Manager
The role of the Manager class is to organize for proper usage of handlers and brokers.
class Manager
{
private List<IGenericHandler> handlers = new List<IGenericHandler>();
public void Register<M>(IHandler<M> handler, IBroker<M> broker) where M : Message
{
handlers.Add(handler);
}
public void Start()
{
foreach ( var handler in handlers )
{
handler.Start();
}
}
public void Stop()
{
foreach (var handler in handlers)
{
handler.Stop();
}
}
}
Messages
For each type of broker, we will define a specific message class, derived from a common base class:
abstract class Message
{
}
class FileHandlerMessage : Message
{
public string FileName { get; set; }
}
Handlers
interface IGenericHandler
{
void Start();
void Stop();
}
interface IHandler<M> : IGenericHandler where M : Message
{
void SetBroker(IBroker<M> broker);
}
class FileHandler : IHandler<FileHandlerMessage>
{
private IBroker<FileHandlerMessage> broker;
public FileHandler()
{
}
public void SetBroker(IBroker<FileHandlerMessage> fileBroker)
{
this.broker = fileBroker;
}
public void Start()
{
// do something
var message = new FileHandlerMessage();
broker.Process(message);
}
public void Stop()
{
// do something
}
}
class TimeHandler : IHandler<TimeHandlerMessage>
{
private IBroker<TimeHandlerMessage> broker;
public void SetBroker(IBroker<TimeHandlerMessage> broker)
{
this.broker = broker;
}
public void Start()
{
// do something
var message = new TimeHandlerMessage();
broker.Process(message);
}
public void Stop()
{
// do something
throw new NotImplementedException();
}
}
Brokers
class FileBroker : IBroker<FileHandlerMessage>
{
public void Process(FileHandlerMessage message)
{
throw new NotImplementedException();
}
}
class TimeBroker : IBroker<TimeHandlerMessage>
{
public void Process(TimeHandlerMessage message)
{
throw new NotImplementedException();
}
}