I've been running into an issue with the MediaPlayer on Lollipop devices. Basically when the device screen is off (i.e. user locked the device) the playback continues, but ends about 1 - 2 seconds too early. This doesn't happen when the screen is on though.
I have an onCompletionListener on the MediaPlayer:
#Override
public void onCompletion(final MediaPlayer mediaPlayer) {
int progress = mediaPlayer.getCurrentPosition();
int duration = mediaPlayer.getDuration();
Log.d("PlaybackController", "progress: " + progress + " duration: " + duration);
Log.d("PlaybackController", "Delay: " + (duration - progress)); // I'm seeing a difference of 1 - 3 seconds :(.
mServiceHandler.postDelayed(new Runnable() {
#Override
public void run() {
broadcastCompleted();
}
}, Math.max(duration - progress, 0));
}
This usually prints: Delay: [1500 - 3000]. I was wondering if there was a wake lock I'm missing, but I'm making the correct locks mentioned here: http://developer.android.com/guide/topics/media/mediaplayer.html, which include a PARTIAL_WAKE_LOCK and a WifiLock. Is there something else I'm missing?
Ok it looks like the issue is Android 5.0.1's experimental MediaPlayer called NuPlayer. NuPlayer is being enabled by default on all Android 5.0.1 devices and is only disabled through Developer Options. I've filed a bug against Android here: https://code.google.com/p/android/issues/detail?id=94069&thanks=94069&ts=1420659450
Here's a sample email you can send your users when they face issues with Media playback on Android 5.0.1 devices:
It looks like this might be a bug on Android's new experimental MediaPlayer called NuPlayer. To fix this, please follow these steps:
Go to Android Settings
Go to "About Phone"
Scroll down to "Build Number" and tap the Build number 7 times.
You'll see a message saying "you are now X steps away from being a developer".
After tapping it 7 times it will say "You are now a developer!"
Go back to the main settings screen and you'll see a new option called "Developer Options" right above "About Phone"
Go into Developer Options and Unselect "Use NuPlayer (experimental)" under the Media section.
Update:
Setting a partial wake lock on the MediaPlayer resolves this problem:
playerToPrepare.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
A partial wake lock shouldn't have too big of an impact, and it seems like MediaPlayer itself cleans this up when playback completes.
-- Original Answer ---
I'm cross-posting my answer from here Prevent my audio app using NuPlayer on Android Lollipop 5.x?, until there's a fix out for NuPlayer, the best you can do is to detect when NuPlayer is enabled and take the user to the Developer Settings.
This approach checks Android's system properties values to see if the user have enabled the use of AwesomePlayer or not under Developer Settings. Since Lollipop have NuPlayer on by default, if this value is disabled, we know NuPlayer will be used.
Drop SystemProperties.java into your project for access to read the system properties, do not change its package name from android.os (it calls through to its corresponding JNI methods, so needs to stay the same).
You can now check if the phone is Lollipop/5.0, if AwesomePlayer is enabled, and act accordingly if it's not (e.g. by opening the Developer Settings):
public void openDeveloperSettingsIfAwesomePlayerNotActivated(final Context context) {
final boolean useAwesome = SystemProperties.getBoolean("persist.sys.media.use-awesome", false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !useAwesome) {
final Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
context.startActivity(intent);
}
}
Related
I'm building an Android media player application that I intend to use to play media (videos, pictures, etc.) on a TV while connected via an HDMI cable.
I want to have the media player app pause when the TV's power status is OFF and want it to play when the TV is turned ON.
How do I detect the TV's power status within my Android application when my Android device is connected to the TV via HDMI?
Both the TV and the Android device have support for HDMI-CEC. The device in question is an ODROID C2. I've seen this functionality on the KODI Android application which has a feature to pause the video when the HDMI-CEC status is OFF, I'm looking to implement this within my app as well.
Any help is appreciated. Thanks in advance!
EDIT: Progress below
I tried reading the status of the HDMI connection from within this file /sys/devices/virtual/switch/hdmi/state. However, this file holds int 1 no matter whether the power status of the connected screen / TV is ON or OFF.
2nd Progress update
I'm still working on this. Will not give up, and once I'm done I will surely post the answer here.
You can listen for changes in HDMI status (0 for unplugged and 1 for plugged) by registering for ACTION_HDMI_AUDIO_PLUG. It reports with status 0 when tv is switched off, switches to any other display medium or HDMI is removed. To read into its technicality, you can check out how hot plug detection works in HDMI. Overall, your app can at all times monitor whether the display can currently play your content or not. I have myself implemented this in a solution (on X96 mini android box & amazon fire-stick) where I needed to ensure that the content was actually being played because it included paid content. Also, I have attached the sample code file.
Note: This solution will only work when android device is HDMI source not sink!
Here's the documentation link too- https://developer.android.com/reference/android/media/AudioManager#ACTION_HDMI_AUDIO_PLUG
private BroadcastReceiver eventReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// pause video
String action = intent.getAction();
switch (action) {
case ACTION_HDMI_AUDIO_PLUG :
// EXTRA_AUDIO_PLUG_STATE: 0 - UNPLUG, 1 - PLUG
Toast.makeText(getApplicationContext(),"HDMI PLUGGED OR UNPLUGGED",Toast.LENGTH_LONG).show();
Log.d("MainActivity", "ACTION_HDMI_AUDIO_PLUG " + intent.getIntExtra(EXTRA_AUDIO_PLUG_STATE, -1));
((TextView)(findViewById(R.id.textView))).setText(((TextView)(findViewById(R.id.textView))).getText().toString().concat("At "+System.nanoTime()+": "+intent.getIntExtra(EXTRA_AUDIO_PLUG_STATE, -1) +"\n"));
break;
}
}
};
#Override
protected void onPause() {
super.onPause();
unregisterReceiver(eventReceiver);
}
#Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_HDMI_AUDIO_PLUG);
registerReceiver(eventReceiver, filter);
}
In Some TV's, You need to monitor that (sys/class/amhdmitx/amhdmitx0/hpd_state) folder for changes by 500 ms Interval. because it'll change from 1 to 0 and again from 0 to 1 within 1 seconds.
The code for handling media buttons from headsets that I use in my Text-to-Speech app works great under Android API 22 through 25 (in older versions of Android they are handled by other, now depreciated means). However under Android 8 "Oreo", both public beta and final release, it does not work. Here is the relevant code:
When the service starts, I create MediaSessionCompact object:
mSession = new MediaSessionCompat(getApplicationContext(), "my.package.name._player_session");
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setActive(true);
mSession.setCallback(myMediaSessionCallback);
PlaybackStateCompat state = new PlaybackStateCompat.Builder()
.setActions(ACTION_PLAY_PAUSE | ACTION_PLAY | ACTION_PAUSE |
ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS |
ACTION_FAST_FORWARD | ACTION_REWIND
)
.setState(PlaybackStateCompat.STATE_PAUSED, 0 /*PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN*/, 1f)
.build();
mSession.setPlaybackState(state);
There is of course session media callback defined:
private MediaSessionCompat.Callback myMediaSessionCallback = new MediaSessionCompat.Callback() {
#Override
public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
// The log output below never appears on "Oreo", nothing comes here.
Log.d(TAG, "callback onMediaButtonEvent() Compat");
MediaButtonIntentReceiver.handleIntent(mediaButtonIntent.getAction(), (KeyEvent) mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT));
return true;
}
#Override
public void onSkipToNext() {
//...
}
// etc. other overrides
};
I also experimented with PendingIntent, using MediaButtonReceiver.buildMediaButtonPendingIntent() and set mSession.setMediaButtonReceiver(pendingIntent) for all the actions I'm interested in, then in my service onStartCommand() I call MediaButtonReceiver.handleIntent(mSession, intent):
// still in the same service:
mSession.setMediaButtonReceiver(
MediaButtonReceiver.buildMediaButtonPendingIntent(
this,
mMediaButtonReceiverComponentName,
ACTION_PLAY));
mSession.setMediaButtonReceiver(
MediaButtonReceiver.buildMediaButtonPendingIntent(
this,
mMediaButtonReceiverComponentName,
ACTION_PAUSE));
mSession.setMediaButtonReceiver(
MediaButtonReceiver.buildMediaButtonPendingIntent(
this,
mMediaButtonReceiverComponentName,
ACTION_PLAY_PAUSE));
and in the service onStartCommand():
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// ...
if (intent != null) {
MediaButtonReceiver.handleIntent(mSession, intent);
// ...
}
return START_NOT_STICKY;
}
Nothing, it's completely dumb to media buttons press events. What's wrong with "O" or my code there??? I'm completely baffled.
Update 8/32/2017
I also created a trivial but working app project that demonstrates the problem, please see: https://github.com/gregko/PlayerServiceSample. This project displays LogCat output when a media button is pressed on a headset under Android 5.x to 7.x, but fails completely under Android 8 "Oreo".
Update 9/1/2017
There is now an open issue on Android Issue Tracker about this, which I submitted, at https://issuetracker.google.com/issues/65175978. Still the media buttons work in several music player apps I tested on Oreo, I just can't figure out what do they do differently to make them work... The context of my app is not playing music, but reading aloud text with Text to Speech service, so a lot of code from Music Player examples does not apply.
Solved. On "Android 8.0 Behavior Changes" Google page we find this text:
In Android 8.0 (API level 26) the handling of media button events is different:
The handling of media buttons in a UI activity has not changed: foreground activities still get priority in handling media button events.
If the foreground activity does not handle the media button event, the system routes the event to the app that most recently played audio locally. The active status, flags, and playback state of a media session are not considered when determining which app receives media button events.
If the app's media session has been released, the system sends the media button event to the app's MediaButtonReceiver if it has one.
For every other case, the system discards the media button event.
All I had to do to make my trivial sample work was to play some sound with MediaPlayer. Apparently playing sound with Text-to-Speech API does not qualify, which in my opinion is a bug.
Here is the code I added to my trivial sample to make it work, playing a very brief and silent WAV file from Raw resources directory:
final MediaPlayer mMediaPlayer;
mMediaPlayer = MediaPlayer.create(this, R.raw.silent_sound);
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
mMediaPlayer.release();
}
});
mMediaPlayer.start();
Update
Submitted the bug report to Android issue tracker at https://issuetracker.google.com/issues/65344811
Update 2, Oct. 10, 2017
Google now says that Oreo behavior in this respect is "by design" and won't fix it. Read the reply near the end of the issue tracker post above. I must say I'm disappointed.
I have an Android application for a Samsung tablet that uses an external device which draws its power from the tablet headphone jack. When the external device is powered on (by programmatically maxing out the volume), Android briefly displays a warning popup saying: "Loud music may harm your hearing if you listen to it for too long..." I would like that message to not be displayed.
Here's the offending line of code:
mAudioMgr.setStreamVolume(AudioManager.STREAM_MUSIC, mAudioMgr.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
It's not an option to only turn it up halfway. In fact, I've seen the tablet display the warning (when changing the volume by hand) even on volume settings lower than the max setting.
And yes, I record the original volume, and restore it when we're done with the external device.
Thanks for any suggestions.
Since you cannot disable the systems message, and you will also have a problem when the user manually lowers the volume, I suggest you do a series of controls which will help with that.
To max the volume:
public void maxVolume() {
AudioManager audioManager = (AudioManager)context.getSystemService(this.AUDIO_SERVICE);
while ( audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) < audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) {
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
}
}
To prevent users from manually changing volume:
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
|| (keyCode == KeyEvent.KEYCODE_VOLUME_UP)
|| (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE)) {
return true;
}
return super.onKeyDown(keyCode, event);
}
You can call maxVolume() in your onCreate method, and later call it just before you start using the jack output procedure to make sure the system has not changed your volume.
Alternatively you can register a listener to listen for volume changes, make maxVolume() static and call it from the listener. Hope it helps you out!
I know that this question is quite old, but I just ran across this issue myself. I figured that I would add to the answers given with what I found in case it helps someone else that happens upon this.
One of the posts here mentions that the warning is not in the AOSP, but this is not true. Please reference the VolumePanel::SafteyWarning class here: VolumePanel::SafetyWarning
Once you peruse through the SafetyWarning class you'll notice that there is a hidden method inside of the AudioManager class called disableSafeMediaVolume. You can, through reflection, invoke this hidden method easily enough, but if you do then you'll get a security exception as it requires the system permission "android.permission.STATUS_BAR_SERVICE".
So... with that said, the dialog as far as I can tell, cannot be programmatically suppressed unless you have this system permission.
What we ended up doing with the blessing of product management is we cache the volume setting in SharedPreferences and then reset it from the cache when our application starts. If it happens to be above the threshold for the warning dialog then the warning will be shown and the user will just have to press OK to get the volume setting that they want, but at least they won't have to manually reset the volume themselves.
A couple of other notes on why some don't see this dialog. You will only see it if you have headphones (or earbuds, or a wired headset) plugged into the device and you attempt to set the media volume stream over a certain threshold (on the Samsung Galaxy Tab A this threshold is 60%). If you press OK at the dialog then it will not be shown again unless you either restart the device or unplug the headphones and plug them back in (with the volume set at or above the threshold).
Hope this helps someone.
I don't know if you can consider this much of an answer but I'm pretty sure you can't remove that message as its displayed by the system automatically and not by something you can access. My Samsung Galaxy S3 does the same thing.
Also from what I can read on the net some android devices also lower the volume when you put in a jack stick so "you don't harm your hearing".
Can you override the notification by displaying your own notification just before or after perhaps? The notification is just a standard Toast.
My code listens to the DCIM folder, using a FileObserver.
All Android versions I used, except 4.1.1, sent only 1 event - when the video was finished taken.
I think it's the correct behavior - write continually and close when finished.
In 4.1.1 (Galaxy Nexus and Nexus S) though, the event FileObserver.CLOSE_WRITE is sent
twice - when the video starts and when it ends.
Also the same for photos - the event is sent twice - though it's not that critical.
The problem is that I can't distinguish between the start event and end event of a video.
I could try and check the size of the file, but because the event may have been delayed (slow/busy device), the size may be quite big.
Any idea why was the behavior changed? Do you know where is the camera's app source code? I can try and look at the history to understand that.
As I wrote in one of my comments, the difference between 4.1 and previous Android versions is that in 4.1.1, the file is written and closed twice. Once when an empty video file is created. Then the video is written into a tmp file. Then the rename/copy of the tmp file is the second write_close event.
In previous versions there's not tmp file - only the original - thus only one close_write event.
Please comment if you think it's a bug. I'm not sure.
I have myself an app which monitors the DCIM/Camera directory through a FileObserver. What I noticed, and could be of help to you, is that the first operation is a CLOSE_WRITE, however the final operation is a MOVED_TO from the .tmp to the real file, which means you can recognize when the video is (really) ready.
My real code is more complex due to the requirements of my app, but the general idea is something like this:
/* My FileObserver implementation field */
private HashSet<String> jbCache = new HashSet(...)
...
protected void onEvent(int event, String path) {
boolean isJellyBean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLYBEAN;
if ((event & FileObserver.CLOSE_WRITE) > 0) {
if (isJellyBean) {
jbCache.add(path);
} else {
performYourWork(path);
}
} else if ((event & FileObserver.MOVED_TO) > 0 && isJellyBean && jbCache.contains(path)) {
performYourWork(path);
jbCache.remove(path);
}
}
You have to listen to both CLOSE_WRITE and MOVED_TO when you register the events you want to catch, obviously.
Although I starred your bug, I doubt Google will ever acknowledge it, as it looks like there could be some (disagreeable) reasoning behind the change. The Camera app is mostly a no-standard crap anyway (e.g.: fake DCIM standard compliance)
My app allows the user to access their corporate voice mail. Normally, durring a phone call when the user holds the device up to their ear, the screen shuts off so they wont accidentally push buttons with their face. I would like to make my app do the same thing when the user is listening to their voice mail.
anyone know how to do this?
If you are allowed to look at open source code without causing yourself problems, check the source of the Android Phone Application. Specifically src/com/android/phone/PhoneApp.java and src/com/android/phone/InCallScreen.java.
From src/com/android/phone/PhoneApp.java:
//Around line 519
// Wake lock used to control proximity sensor behavior.
if ((pm.getSupportedWakeLockFlags()
& PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) != 0x0) {
mProximityWakeLock = pm.newWakeLock(
PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
LOG_TAG);
}
....
// Around line 1334
if (((state == Phone.State.OFFHOOK) || mBeginningCall)&& !screenOnImmediately) {
// Phone is in use! Arrange for the screen to turn off
// automatically when the sensor detects a close object.
if (!mProximityWakeLock.isHeld()) {
if (DBG) Log.d(LOG_TAG, "updateProximitySensorMode: acquiring...");
mProximityWakeLock.acquire();
} else {
if (VDBG) Log.d(LOG_TAG, "updateProximitySensorMode: lock already held.");
}
} else {
// Phone is either idle, or ringing. We don't want any
// special proximity sensor behavior in either case.
if (mProximityWakeLock.isHeld()) {
if (DBG) Log.d(LOG_TAG, "updateProximitySensorMode: releasing...");
// Wait until user has moved the phone away from his head if we are
// releasing due to the phone call ending.
// Qtherwise, turn screen on immediately
int flags =
(screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE);
mProximityWakeLock.release(flags);
}
}
Additionally, if you look at the code for the PowerManager class, PROXIMITY_SCREEN_OFF_WAKE_LOCK is documented (but hidden) and should do what you want ( I am not sure which API level this works for, however ) -- but not in the table for some reason.
/**
* Wake lock that turns the screen off when the proximity sensor activates.
* Since not all devices have proximity sensors, use
* {#link #getSupportedWakeLockFlags() getSupportedWakeLockFlags()} to determine if
* this wake lock mode is supported.
*
* {#hide}
*/
public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = WAKE_BIT_PROXIMITY_SCREEN_OFF;
If you aren't afraid of using a potential undocumented feature, it should do exactly what you need.
as of API level 21 (Lollipop) you can get proximity wake lock this just like that:
if(powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
wakeLock.setReferenceCounted(false);
return wakeLock;
} else {
return null;
}
}
then it is up to you to acquire and release the lock.
PS: PowerManager#getSupportedWakeLockFlags was hidden, but now exists nomore. They have invented isWakeLockLevelSupported instead.
Probably you don't need it anymore but for the ones that are interested in code you could have a look at my SpeakerProximity project at http://code.google.com/p/speakerproximity/
What you are seeing is the use of a proximity sensor. For devices that have one, you access it through SensorManager.