1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/media/session/media_session_android.h"
6
7 #include <utility>
8
9 #include "base/android/jni_array.h"
10 #include "base/time/time.h"
11 #include "content/browser/media/session/media_session_impl.h"
12 #include "content/browser/web_contents/web_contents_android.h"
13 #include "content/browser/web_contents/web_contents_impl.h"
14 #include "content/public/android/content_jni_headers/MediaSessionImpl_jni.h"
15 #include "content/public/browser/media_session.h"
16 #include "services/media_session/public/cpp/media_image.h"
17 #include "services/media_session/public/cpp/media_position.h"
18 #include "services/media_session/public/mojom/audio_focus.mojom.h"
19
20 namespace content {
21
22 using base::android::JavaParamRef;
23 using base::android::ScopedJavaLocalRef;
24
25 struct MediaSessionAndroid::JavaObjectGetter {
GetJavaObjectcontent::MediaSessionAndroid::JavaObjectGetter26 static ScopedJavaLocalRef<jobject> GetJavaObject(
27 MediaSessionAndroid* session_android) {
28 return session_android->GetJavaObject();
29 }
30 };
31
MediaSessionAndroid(MediaSessionImpl * session)32 MediaSessionAndroid::MediaSessionAndroid(MediaSessionImpl* session)
33 : media_session_(session) {
34 DCHECK(media_session_);
35
36 JNIEnv* env = base::android::AttachCurrentThread();
37 ScopedJavaLocalRef<jobject> j_media_session =
38 Java_MediaSessionImpl_create(env, reinterpret_cast<intptr_t>(this));
39 j_media_session_ = JavaObjectWeakGlobalRef(env, j_media_session);
40
41 WebContentsAndroid* contents_android = GetWebContentsAndroid();
42 if (contents_android)
43 contents_android->SetMediaSession(j_media_session);
44
45 session->AddObserver(observer_receiver_.BindNewPipeAndPassRemote());
46 }
47
~MediaSessionAndroid()48 MediaSessionAndroid::~MediaSessionAndroid() {
49 JNIEnv* env = base::android::AttachCurrentThread();
50 ScopedJavaLocalRef<jobject> j_local_session = j_media_session_.get(env);
51
52 // The Java object will tear down after this call.
53 if (!j_local_session.is_null())
54 Java_MediaSessionImpl_mediaSessionDestroyed(env, j_local_session);
55
56 j_media_session_.reset();
57
58 WebContentsAndroid* contents_android = GetWebContentsAndroid();
59 if (contents_android)
60 contents_android->SetMediaSession(nullptr);
61 }
62
63 // static
JNI_MediaSessionImpl_GetMediaSessionFromWebContents(JNIEnv * env,const JavaParamRef<jobject> & j_contents_android)64 ScopedJavaLocalRef<jobject> JNI_MediaSessionImpl_GetMediaSessionFromWebContents(
65 JNIEnv* env,
66 const JavaParamRef<jobject>& j_contents_android) {
67 WebContents* contents = WebContents::FromJavaWebContents(j_contents_android);
68 if (!contents)
69 return ScopedJavaLocalRef<jobject>();
70
71 MediaSessionImpl* session = MediaSessionImpl::Get(contents);
72 DCHECK(session);
73 return MediaSessionAndroid::JavaObjectGetter::GetJavaObject(
74 session->session_android());
75 }
76
MediaSessionInfoChanged(media_session::mojom::MediaSessionInfoPtr session_info)77 void MediaSessionAndroid::MediaSessionInfoChanged(
78 media_session::mojom::MediaSessionInfoPtr session_info) {
79 ScopedJavaLocalRef<jobject> j_local_session = GetJavaObject();
80 if (j_local_session.is_null())
81 return;
82
83 bool is_paused = session_info->playback_state ==
84 media_session::mojom::MediaPlaybackState::kPaused;
85
86 // The media session info changed event might be called more often than we
87 // need to notify Android since we only need a couple of bits of data.
88 // Therefore, we only notify Android if the bits have changed.
89 if (is_paused == is_paused_ &&
90 is_controllable_ == session_info->is_controllable) {
91 return;
92 }
93
94 is_paused_ = is_paused;
95 is_controllable_ = session_info->is_controllable;
96
97 JNIEnv* env = base::android::AttachCurrentThread();
98 Java_MediaSessionImpl_mediaSessionStateChanged(
99 env, j_local_session, session_info->is_controllable, is_paused);
100 }
101
MediaSessionMetadataChanged(const base::Optional<media_session::MediaMetadata> & metadata)102 void MediaSessionAndroid::MediaSessionMetadataChanged(
103 const base::Optional<media_session::MediaMetadata>& metadata) {
104 ScopedJavaLocalRef<jobject> j_local_session = GetJavaObject();
105 if (j_local_session.is_null())
106 return;
107
108 JNIEnv* env = base::android::AttachCurrentThread();
109
110 // Avoid translating metadata through JNI if there is no Java observer.
111 if (!Java_MediaSessionImpl_hasObservers(env, j_local_session))
112 return;
113
114 ScopedJavaLocalRef<jobject> j_metadata;
115 if (metadata.has_value())
116 j_metadata = metadata.value().CreateJavaObject(env);
117 Java_MediaSessionImpl_mediaSessionMetadataChanged(env, j_local_session,
118 j_metadata);
119 }
120
MediaSessionActionsChanged(const std::vector<media_session::mojom::MediaSessionAction> & actions)121 void MediaSessionAndroid::MediaSessionActionsChanged(
122 const std::vector<media_session::mojom::MediaSessionAction>& actions) {
123 ScopedJavaLocalRef<jobject> j_local_session = GetJavaObject();
124 if (j_local_session.is_null())
125 return;
126
127 std::vector<int> actions_vec;
128 for (auto action : actions)
129 actions_vec.push_back(static_cast<int>(action));
130
131 JNIEnv* env = base::android::AttachCurrentThread();
132 Java_MediaSessionImpl_mediaSessionActionsChanged(
133 env, j_local_session, base::android::ToJavaIntArray(env, actions_vec));
134 }
135
MediaSessionImagesChanged(const base::flat_map<media_session::mojom::MediaSessionImageType,std::vector<media_session::MediaImage>> & images)136 void MediaSessionAndroid::MediaSessionImagesChanged(
137 const base::flat_map<media_session::mojom::MediaSessionImageType,
138 std::vector<media_session::MediaImage>>& images) {
139 ScopedJavaLocalRef<jobject> j_local_session = GetJavaObject();
140 if (j_local_session.is_null())
141 return;
142
143 JNIEnv* env = base::android::AttachCurrentThread();
144
145 // Android is only interested in the artwork images.
146 auto it = images.find(media_session::mojom::MediaSessionImageType::kArtwork);
147 if (it == images.end())
148 return;
149
150 // Avoid translating metadata through JNI if there is no Java observer.
151 if (!Java_MediaSessionImpl_hasObservers(env, j_local_session))
152 return;
153
154 Java_MediaSessionImpl_mediaSessionArtworkChanged(
155 env, j_local_session,
156 media_session::MediaImage::ToJavaArray(env, it->second));
157 }
158
MediaSessionPositionChanged(const base::Optional<media_session::MediaPosition> & position)159 void MediaSessionAndroid::MediaSessionPositionChanged(
160 const base::Optional<media_session::MediaPosition>& position) {
161 ScopedJavaLocalRef<jobject> j_local_session = GetJavaObject();
162 if (j_local_session.is_null())
163 return;
164
165 JNIEnv* env = base::android::AttachCurrentThread();
166
167 if (position) {
168 Java_MediaSessionImpl_mediaSessionPositionChanged(
169 env, j_local_session, position->CreateJavaObject(env));
170 } else {
171 Java_MediaSessionImpl_mediaSessionPositionChanged(env, j_local_session,
172 nullptr);
173 }
174 }
175
Resume(JNIEnv * env,const base::android::JavaParamRef<jobject> & j_obj)176 void MediaSessionAndroid::Resume(
177 JNIEnv* env,
178 const base::android::JavaParamRef<jobject>& j_obj) {
179 DCHECK(media_session_);
180 media_session_->Resume(MediaSession::SuspendType::kUI);
181 }
182
Suspend(JNIEnv * env,const base::android::JavaParamRef<jobject> & j_obj)183 void MediaSessionAndroid::Suspend(
184 JNIEnv* env,
185 const base::android::JavaParamRef<jobject>& j_obj) {
186 DCHECK(media_session_);
187 media_session_->Suspend(MediaSession::SuspendType::kUI);
188 }
189
Stop(JNIEnv * env,const base::android::JavaParamRef<jobject> & j_obj)190 void MediaSessionAndroid::Stop(
191 JNIEnv* env,
192 const base::android::JavaParamRef<jobject>& j_obj) {
193 DCHECK(media_session_);
194 media_session_->Stop(MediaSession::SuspendType::kUI);
195 }
196
Seek(JNIEnv * env,const base::android::JavaParamRef<jobject> & j_obj,const jlong millis)197 void MediaSessionAndroid::Seek(
198 JNIEnv* env,
199 const base::android::JavaParamRef<jobject>& j_obj,
200 const jlong millis) {
201 DCHECK(media_session_);
202 DCHECK_NE(millis, 0)
203 << "Attempted to seek by a missing number of milliseconds";
204 media_session_->Seek(base::TimeDelta::FromMilliseconds(millis));
205 }
206
SeekTo(JNIEnv * env,const base::android::JavaParamRef<jobject> & j_obj,const jlong millis)207 void MediaSessionAndroid::SeekTo(
208 JNIEnv* env,
209 const base::android::JavaParamRef<jobject>& j_obj,
210 const jlong millis) {
211 DCHECK(media_session_);
212 DCHECK_GT(millis, 0) << "Attempted to seek to a negative position";
213 media_session_->SeekTo(base::TimeDelta::FromMilliseconds(millis));
214 }
215
DidReceiveAction(JNIEnv * env,const JavaParamRef<jobject> & obj,int action)216 void MediaSessionAndroid::DidReceiveAction(JNIEnv* env,
217 const JavaParamRef<jobject>& obj,
218 int action) {
219 media_session_->DidReceiveAction(
220 static_cast<media_session::mojom::MediaSessionAction>(action));
221 }
222
RequestSystemAudioFocus(JNIEnv * env,const base::android::JavaParamRef<jobject> & j_obj)223 void MediaSessionAndroid::RequestSystemAudioFocus(
224 JNIEnv* env,
225 const base::android::JavaParamRef<jobject>& j_obj) {
226 DCHECK(media_session_);
227 media_session_->RequestSystemAudioFocus(
228 media_session::mojom::AudioFocusType::kGain);
229 }
230
GetWebContentsAndroid()231 WebContentsAndroid* MediaSessionAndroid::GetWebContentsAndroid() {
232 WebContentsImpl* contents =
233 static_cast<WebContentsImpl*>(media_session_->web_contents());
234 if (!contents)
235 return nullptr;
236 return contents->GetWebContentsAndroid();
237 }
238
GetJavaObject()239 ScopedJavaLocalRef<jobject> MediaSessionAndroid::GetJavaObject() {
240 JNIEnv* env = base::android::AttachCurrentThread();
241 return j_media_session_.get(env);
242 }
243
244 } // namespace content
245