diff options
Diffstat (limited to 'media')
| -rw-r--r-- | media/java/android/media/AudioRecord.java | 166 | ||||
| -rw-r--r-- | media/java/android/media/MediaDescription.java | 32 | ||||
| -rw-r--r-- | media/java/android/media/MediaRecorder.java | 7 | ||||
| -rw-r--r-- | media/java/android/media/MediaRouter.java | 6 | ||||
| -rw-r--r-- | media/java/android/media/session/ISessionCallback.aidl | 5 | ||||
| -rw-r--r-- | media/java/android/media/session/ISessionController.aidl | 5 | ||||
| -rw-r--r-- | media/java/android/media/session/MediaController.java | 27 | ||||
| -rw-r--r-- | media/java/android/media/session/MediaSession.java | 23 | ||||
| -rw-r--r-- | media/java/android/media/session/PlaybackState.java | 17 | ||||
| -rw-r--r-- | media/java/android/media/tv/TvInputService.java | 12 |
10 files changed, 281 insertions, 19 deletions
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 259fe37..99b7bee 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -20,6 +20,7 @@ import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.util.Iterator; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Binder; import android.os.Handler; @@ -313,8 +314,14 @@ public class AudioRecord audioParamCheck(attributes.getCapturePreset(), rate, encoding); - mChannelCount = AudioFormat.channelCountFromInChannelMask(format.getChannelMask()); - mChannelMask = getChannelMaskFromLegacyConfig(format.getChannelMask(), false); + int channelMask = AudioFormat.CHANNEL_IN_DEFAULT; + if ((format.getPropertySetMask() + & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK) != 0) + { + channelMask = format.getChannelMask(); + } + mChannelCount = AudioFormat.channelCountFromInChannelMask(channelMask); + mChannelMask = getChannelMaskFromLegacyConfig(channelMask, false); audioBuffSizeCheck(bufferSizeInBytes); @@ -335,6 +342,161 @@ public class AudioRecord mState = STATE_INITIALIZED; } + /** + * Builder class for {@link AudioRecord} objects. + * Use this class to configure and create an <code>AudioRecord</code> instance. By setting the + * recording preset (a.k.a. recording source) and audio format parameters, you indicate which of + * those vary from the default behavior on the device. + * <p> Here is an example where <code>Builder</code> is used to specify all {@link AudioFormat} + * parameters, to be used by a new <code>AudioRecord</code> instance: + * + * <pre class="prettyprint"> + * AudioRecord recorder = new AudioRecord.Builder() + * .setCapturePreset(MediaRecorder.AudioSource.VOICE_COMMUNICATION) + * .setAudioFormat(new AudioFormat.Builder() + * .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + * .setSampleRate(32000) + * .setChannelMask(AudioFormat.CHANNEL_IN_MONO) + * .build()) + * .setBufferSize(2*minBuffSize) + * .build(); + * </pre> + * <p> + * If the capture preset is not set with {@link #setCapturePreset(int)}, + * {@link MediaRecorder.AudioSource#DEFAULT} is used. + * <br>If the audio format is not specified or is incomplete, its sample rate will be the + * default output sample rate of the device (see + * {@link AudioManager#PROPERTY_OUTPUT_SAMPLE_RATE}), its channel configuration will be + * {@link AudioFormat#CHANNEL_IN_DEFAULT}. + * <br>Failing to set an adequate buffer size with {@link #setBufferSizeInBytes(int)} will + * prevent the successful creation of an <code>AudioRecord</code> instance. + */ + public static class Builder { + private AudioAttributes mAttributes; + private AudioFormat mFormat; + private int mBufferSizeInBytes; + private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE; + + /** + * Constructs a new Builder with the default values as described above. + */ + public Builder() { + } + + /** + * @param preset the capture preset (also referred to as the recording source). + * See {@link MediaRecorder.AudioSource} for the supported capture preset definitions. + * @return the same Builder instance. + * @throws IllegalArgumentException + */ + public Builder setCapturePreset(int preset) throws IllegalArgumentException { + if ( (preset < MediaRecorder.AudioSource.DEFAULT) || + (preset > MediaRecorder.getAudioSourceMax()) ) { + throw new IllegalArgumentException("Invalid audio source " + preset); + } + mAttributes = new AudioAttributes.Builder() + .setInternalCapturePreset(preset) + .build(); + return this; + } + + /** + * @hide + * To be only used by system components. Allows specifying non-public capture presets + * @param attributes a non-null {@link AudioAttributes} instance that contains the capture + * preset to be used. + * @return the same Builder instance. + * @throws IllegalArgumentException + */ + @SystemApi + public Builder setAudioAttributes(@NonNull AudioAttributes attributes) + throws IllegalArgumentException { + if (attributes == null) { + throw new IllegalArgumentException("Illegal null AudioAttributes argument"); + } + if (attributes.getCapturePreset() == MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID) { + throw new IllegalArgumentException( + "No valid capture preset in AudioAttributes argument"); + } + // keep reference, we only copy the data when building + mAttributes = attributes; + return this; + } + + /** + * Sets the format of the audio data to be captured. + * @param format a non-null {@link AudioFormat} instance + * @return the same Builder instance. + * @throws IllegalArgumentException + */ + public Builder setAudioFormat(@NonNull AudioFormat format) throws IllegalArgumentException { + if (format == null) { + throw new IllegalArgumentException("Illegal null AudioFormat argument"); + } + // keep reference, we only copy the data when building + mFormat = format; + return this; + } + + /** + * Sets the total size (in bytes) of the buffer where audio data is written + * during the recording. New audio data can be read from this buffer in smaller chunks + * than this size. See {@link #getMinBufferSize(int, int, int)} to determine the minimum + * required buffer size for the successful creation of an AudioRecord instance. + * Using values smaller than getMinBufferSize() will result in an initialization failure. + * @param bufferSizeInBytes a value strictly greater than 0 + * @return the same Builder instance. + * @throws IllegalArgumentException + */ + public Builder setBufferSizeInBytes(int bufferSizeInBytes) throws IllegalArgumentException { + if (bufferSizeInBytes <= 0) { + throw new IllegalArgumentException("Invalid buffer size " + bufferSizeInBytes); + } + mBufferSizeInBytes = bufferSizeInBytes; + return this; + } + + /** + * @hide + * To be only used by system components. + * @param sessionId ID of audio session the AudioRecord must be attached to, or + * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at + * construction time. + * @return the same Builder instance. + * @throws IllegalArgumentException + */ + @SystemApi + public Builder setSessionId(int sessionId) throws IllegalArgumentException { + if (sessionId < 0) { + throw new IllegalArgumentException("Invalid session ID " + sessionId); + } + mSessionId = sessionId; + return this; + } + + /** + * @return a new {@link AudioRecord} instance initialized with all the parameters set + * on this <code>Builder</code> + * @throws UnsupportedOperationException if the parameters set on the <code>Builder</code> + * were incompatible, or if they are not supported by the device. + */ + public AudioRecord build() throws UnsupportedOperationException { + if (mFormat == null) { + mFormat = new AudioFormat.Builder().build(); + } + if (mAttributes == null) { + mAttributes = new AudioAttributes.Builder() + .setInternalCapturePreset(MediaRecorder.AudioSource.DEFAULT) + .build(); + } + try { + return new AudioRecord(mAttributes, mFormat, mBufferSizeInBytes, mSessionId); + } catch (IllegalArgumentException e) { + throw new UnsupportedOperationException(e.getMessage()); + } + } + } + // Convenience method for the constructor's parameter checks. // This, getChannelMaskFromLegacyConfig and audioBuffSizeCheck are where constructor // IllegalArgumentException-s are thrown diff --git a/media/java/android/media/MediaDescription.java b/media/java/android/media/MediaDescription.java index ddbffc2..afc3ca7 100644 --- a/media/java/android/media/MediaDescription.java +++ b/media/java/android/media/MediaDescription.java @@ -41,9 +41,13 @@ public class MediaDescription implements Parcelable { * Extras for opaque use by apps/system. */ private final Bundle mExtras; + /** + * A Uri to identify this content. + */ + private final Uri mMediaUri; private MediaDescription(String mediaId, CharSequence title, CharSequence subtitle, - CharSequence description, Bitmap icon, Uri iconUri, Bundle extras) { + CharSequence description, Bitmap icon, Uri iconUri, Bundle extras, Uri mediaUri) { mMediaId = mediaId; mTitle = title; mSubtitle = subtitle; @@ -51,6 +55,7 @@ public class MediaDescription implements Parcelable { mIcon = icon; mIconUri = iconUri; mExtras = extras; + mMediaUri = mediaUri; } private MediaDescription(Parcel in) { @@ -61,6 +66,7 @@ public class MediaDescription implements Parcelable { mIcon = in.readParcelable(null); mIconUri = in.readParcelable(null); mExtras = in.readBundle(); + mMediaUri = in.readParcelable(null); } /** @@ -125,6 +131,15 @@ public class MediaDescription implements Parcelable { return mExtras; } + /** + * Returns a Uri representing this content or null. + * + * @return A media Uri or null. + */ + public @Nullable Uri getMediaUri() { + return mMediaUri; + } + @Override public int describeContents() { return 0; @@ -139,6 +154,7 @@ public class MediaDescription implements Parcelable { dest.writeParcelable(mIcon, flags); dest.writeParcelable(mIconUri, flags); dest.writeBundle(mExtras); + dest.writeParcelable(mMediaUri, flags); } @Override @@ -170,6 +186,7 @@ public class MediaDescription implements Parcelable { private Bitmap mIcon; private Uri mIconUri; private Bundle mExtras; + private Uri mMediaUri; /** * Creates an initially empty builder. @@ -257,9 +274,20 @@ public class MediaDescription implements Parcelable { return this; } + /** + * Sets the media uri. + * + * @param mediaUri The content's {@link Uri} for the item or null. + * @return this + */ + public Builder setMediaUri(@Nullable Uri mediaUri) { + mMediaUri = mediaUri; + return this; + } + public MediaDescription build() { return new MediaDescription(mMediaId, mTitle, mSubtitle, mDescription, mIcon, mIconUri, - mExtras); + mExtras, mMediaUri); } } } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 58c86f2..058cfd2 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -157,8 +157,11 @@ public class MediaRecorder } /** - * Defines the audio source. These constants are used with - * {@link MediaRecorder#setAudioSource(int)}. + * Defines the audio source. + * An audio source defines both a default physical source of audio signal, and a recording + * configuration; it's also known as a capture preset. These constants are for instance used + * in {@link MediaRecorder#setAudioSource(int)} or + * {@link AudioRecord.Builder#setCapturePreset(int)}. */ public final class AudioSource { diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index b4c612a..c227eb7 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -1500,18 +1500,18 @@ public class MediaRouter { /** * The default playback type, "local", indicating the presentation of the media is happening - * on the same device (e.g. a phone, a tablet) as where it is controlled from. + * on the same device (e.g. a phone, a tablet) as where it is controlled from. * @see #getPlaybackType() */ public final static int PLAYBACK_TYPE_LOCAL = 0; /** * A playback type indicating the presentation of the media is happening on - * a different device (i.e. the remote device) than where it is controlled from. + * a different device (i.e. the remote device) than where it is controlled from. * @see #getPlaybackType() */ public final static int PLAYBACK_TYPE_REMOTE = 1; /** - * Playback information indicating the playback volume is fixed, i.e. it cannot be + * Playback information indicating the playback volume is fixed, i.e. it cannot be * controlled from this object. An example of fixed playback volume is a remote player, * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather * than attenuate at the source. diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl index 49087b0..adb6b06 100644 --- a/media/java/android/media/session/ISessionCallback.aidl +++ b/media/java/android/media/session/ISessionCallback.aidl @@ -15,8 +15,8 @@ package android.media.session; -import android.media.Rating; import android.content.Intent; +import android.media.Rating; import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; @@ -30,8 +30,9 @@ oneway interface ISessionCallback { // These callbacks are for the TransportPerformer void onPlay(); - void onPlayFromMediaId(String uri, in Bundle extras); + void onPlayFromMediaId(String mediaId, in Bundle extras); void onPlayFromSearch(String query, in Bundle extras); + void onPlayFromUri(in Uri uri, in Bundle extras); void onSkipToTrack(long id); void onPause(); void onStop(); diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl index e2d06d3..8d58a60 100644 --- a/media/java/android/media/session/ISessionController.aidl +++ b/media/java/android/media/session/ISessionController.aidl @@ -23,9 +23,9 @@ import android.media.Rating; import android.media.routing.IMediaRouterDelegate; import android.media.routing.IMediaRouterStateCallback; import android.media.session.ISessionControllerCallback; +import android.media.session.MediaSession; import android.media.session.ParcelableVolumeInfo; import android.media.session.PlaybackState; -import android.media.session.MediaSession; import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; @@ -55,8 +55,9 @@ interface ISessionController { // These commands are for the TransportControls void play(); - void playFromMediaId(String uri, in Bundle extras); + void playFromMediaId(String mediaId, in Bundle extras); void playFromSearch(String string, in Bundle extras); + void playFromUri(in Uri uri, in Bundle extras); void skipToQueueItem(long id); void pause(); void stop(); diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index c23a139..dd81a22 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -516,8 +516,8 @@ public final class MediaController { } /** - * Callback for receiving updates on from the session. A Callback can be - * registered using {@link #registerCallback} + * Callback for receiving updates from the session. A Callback can be + * registered using {@link #registerCallback}. */ public static abstract class Callback { /** @@ -615,9 +615,9 @@ public final class MediaController { } /** - * Request that the player start playback for a specific {@link Uri}. + * Request that the player start playback for a specific media id. * - * @param mediaId The uri of the requested media. + * @param mediaId The id of the requested media. * @param extras Optional extras that can include extra information about the media item * to be played. */ @@ -656,6 +656,25 @@ public final class MediaController { } /** + * Request that the player start playback for a specific {@link Uri}. + * + * @param uri The URI of the requested media. + * @param extras Optional extras that can include extra information about the media item + * to be played. + */ + public void playFromUri(Uri uri, Bundle extras) { + if (uri == null || Uri.EMPTY.equals(uri)) { + throw new IllegalArgumentException( + "You must specify a non-empty Uri for playFromUri."); + } + try { + mSessionBinder.playFromUri(uri, extras); + } catch (RemoteException e) { + Log.wtf(TAG, "Error calling play(" + uri + ").", e); + } + } + + /** * Play an item with a specific id in the play queue. If you specify an * id that is not in the play queue, the behavior is undefined. */ diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index cc602c9..cee82b4 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -30,6 +30,7 @@ import android.media.MediaMetadata; import android.media.Rating; import android.media.VolumeProvider; import android.media.routing.MediaRouter; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -541,6 +542,10 @@ public final class MediaSession { postToCallback(CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras); } + private void dispatchPlayFromUri(Uri uri, Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_PLAY_URI, uri, extras); + } + private void dispatchSkipToItem(long id) { postToCallback(CallbackMessageHandler.MSG_SKIP_TO_ITEM, id); } @@ -833,6 +838,12 @@ public final class MediaSession { } /** + * Override to handle requests to play a specific media item represented by a URI. + */ + public void onPlayFromUri(Uri uri, Bundle extras) { + } + + /** * Override to handle requests to play an item with a given id from the * play queue. */ @@ -961,6 +972,14 @@ public final class MediaSession { } @Override + public void onPlayFromUri(Uri uri, Bundle extras) { + MediaSession session = mMediaSession.get(); + if (session != null) { + session.dispatchPlayFromUri(uri, extras); + } + } + + @Override public void onSkipToTrack(long id) { MediaSession session = mMediaSession.get(); if (session != null) { @@ -1171,6 +1190,7 @@ public final class MediaSession { private static final int MSG_COMMAND = 15; private static final int MSG_ADJUST_VOLUME = 16; private static final int MSG_SET_VOLUME = 17; + private static final int MSG_PLAY_URI = 18; private MediaSession.Callback mCallback; @@ -1210,6 +1230,9 @@ public final class MediaSession { case MSG_PLAY_SEARCH: mCallback.onPlayFromSearch((String) msg.obj, msg.getData()); break; + case MSG_PLAY_URI: + mCallback.onPlayFromUri((Uri) msg.obj, msg.getData()); + break; case MSG_SKIP_TO_ITEM: mCallback.onSkipToQueueItem((Long) msg.obj); break; diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java index 6807e7f..bbe04b5 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/java/android/media/session/PlaybackState.java @@ -126,6 +126,13 @@ public final class PlaybackState implements Parcelable { public static final long ACTION_SKIP_TO_QUEUE_ITEM = 1 << 12; /** + * Indicates this session supports the play from URI command. + * + * @see Builder#setActions(long) + */ + public static final long ACTION_PLAY_FROM_URI = 1 << 13; + + /** * This is the default playback state and indicates that no media has been * added yet, or the performer has been reset and has no content to play. * @@ -353,6 +360,11 @@ public final class PlaybackState implements Parcelable { * <li> {@link PlaybackState#ACTION_SKIP_TO_NEXT}</li> * <li> {@link PlaybackState#ACTION_SEEK_TO}</li> * <li> {@link PlaybackState#ACTION_SET_RATING}</li> + * <li> {@link PlaybackState#ACTION_PLAY_PAUSE}</li> + * <li> {@link PlaybackState#ACTION_PLAY_FROM_MEDIA_ID}</li> + * <li> {@link PlaybackState#ACTION_PLAY_FROM_SEARCH}</li> + * <li> {@link PlaybackState#ACTION_SKIP_TO_QUEUE_ITEM}</li> + * <li> {@link PlaybackState#ACTION_PLAY_FROM_URI}</li> * </ul> */ public long getActions() { @@ -868,6 +880,11 @@ public final class PlaybackState implements Parcelable { * <li> {@link PlaybackState#ACTION_SKIP_TO_NEXT}</li> * <li> {@link PlaybackState#ACTION_SEEK_TO}</li> * <li> {@link PlaybackState#ACTION_SET_RATING}</li> + * <li> {@link PlaybackState#ACTION_PLAY_PAUSE}</li> + * <li> {@link PlaybackState#ACTION_PLAY_FROM_MEDIA_ID}</li> + * <li> {@link PlaybackState#ACTION_PLAY_FROM_SEARCH}</li> + * <li> {@link PlaybackState#ACTION_SKIP_TO_QUEUE_ITEM}</li> + * <li> {@link PlaybackState#ACTION_PLAY_FROM_URI}</li> * </ul> * * @param actions The set of actions allowed. diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index b887855..8ed383a 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -1077,12 +1077,19 @@ public abstract class TvInputService extends Service { int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) { if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")"); boolean isNavigationKey = false; + boolean skipDispatchToOverlayView = false; if (event instanceof KeyEvent) { KeyEvent keyEvent = (KeyEvent) event; - isNavigationKey = isNavigationKey(keyEvent.getKeyCode()); if (keyEvent.dispatch(this, mDispatcherState, this)) { return TvInputManager.Session.DISPATCH_HANDLED; } + isNavigationKey = isNavigationKey(keyEvent.getKeyCode()); + // When media keys and KEYCODE_MEDIA_AUDIO_TRACK are dispatched to ViewRootImpl, + // ViewRootImpl always consumes the keys. In this case, an application loses + // a chance to handle media keys. Therefore, media keys are not dispatched to + // ViewRootImpl. + skipDispatchToOverlayView = KeyEvent.isMediaKey(keyEvent.getKeyCode()) + || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK; } else if (event instanceof MotionEvent) { MotionEvent motionEvent = (MotionEvent) event; final int source = motionEvent.getSource(); @@ -1100,7 +1107,8 @@ public abstract class TvInputService extends Service { } } } - if (mOverlayViewContainer == null || !mOverlayViewContainer.isAttachedToWindow()) { + if (mOverlayViewContainer == null || !mOverlayViewContainer.isAttachedToWindow() + || skipDispatchToOverlayView) { return TvInputManager.Session.DISPATCH_NOT_HANDLED; } if (!mOverlayViewContainer.hasWindowFocus()) { |
