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