Merge lp:~jhodapp/media-hub/fix-zero-position-while-seeking into lp:media-hub
- fix-zero-position-while-seeking
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Ricardo Mendoza |
Approved revision: | 124 |
Merged at revision: | 126 |
Proposed branch: | lp:~jhodapp/media-hub/fix-zero-position-while-seeking |
Merge into: | lp:media-hub |
Prerequisite: | lp:~thomas-voss/media-hub/decouple-player-skeleton-and-implementation |
Diff against target: |
3163 lines (+1445/-633) 34 files modified
debian/changelog (+6/-9) debian/control (+0/-6) include/core/media/player.h (+1/-0) src/core/media/CMakeLists.txt (+6/-2) src/core/media/apparmor.h (+0/-93) src/core/media/apparmor/context.cpp (+34/-0) src/core/media/apparmor/context.h (+51/-0) src/core/media/apparmor/dbus.h (+94/-0) src/core/media/apparmor/ubuntu.cpp (+198/-0) src/core/media/apparmor/ubuntu.h (+167/-0) src/core/media/gstreamer/engine.cpp (+3/-3) src/core/media/gstreamer/playbin.cpp (+23/-6) src/core/media/gstreamer/playbin.h (+2/-0) src/core/media/hashed_keyed_player_store.cpp (+80/-0) src/core/media/hashed_keyed_player_store.h (+76/-0) src/core/media/keyed_player_store.h (+83/-0) src/core/media/mpris/player.h (+3/-0) src/core/media/null_track_list.h (+114/-0) src/core/media/player.cpp (+0/-1) src/core/media/player_configuration.h (+0/-3) src/core/media/player_implementation.cpp (+104/-81) src/core/media/player_implementation.h (+9/-9) src/core/media/player_skeleton.cpp (+35/-93) src/core/media/player_skeleton.h (+22/-16) src/core/media/player_stub.cpp (+18/-0) src/core/media/player_stub.h (+1/-0) src/core/media/power/state_controller.cpp (+21/-25) src/core/media/server/server.cpp (+19/-4) src/core/media/service_implementation.cpp (+35/-27) src/core/media/service_implementation.h (+2/-1) src/core/media/service_skeleton.cpp (+125/-174) src/core/media/service_skeleton.h (+14/-25) src/core/media/telephony/call_monitor.cpp (+56/-37) src/core/media/telephony/call_monitor.h (+43/-18) |
To merge this branch: | bzr merge lp:~jhodapp/media-hub/fix-zero-position-while-seeking |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Ubuntu Phablet Team | Pending | ||
Review via email: mp+252605@code.launchpad.net |
Commit message
* Prevent a 0 position from being reported to the app which happens while seeking. Covers bad behavior that happens from GStreamer. Also expose the about_to_finish signal to the client.
* Enable playback again after manually seeking all the way to EOS. Also only send VideoDimensions
Description of the change
* Prevent a 0 position from being reported to the app which happens while seeking. Covers bad behavior that happens from GStreamer. Also expose the about_to_finish signal to the client.
* Enable playback again after manually seeking all the way to EOS. Also only send VideoDimensions
PS Jenkins bot (ps-jenkins) wrote : | # |
- 122. By Jim Hodapp
-
Merged prereqs.
- 123. By Jim Hodapp
-
Merge prereqs.
- 124. By Jim Hodapp
-
Fix the last merge.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:124
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'debian/changelog' |
2 | --- debian/changelog 2015-03-16 15:38:27 +0000 |
3 | +++ debian/changelog 2015-03-16 15:38:27 +0000 |
4 | @@ -1,4 +1,9 @@ |
5 | -<<<<<<< TREE |
6 | +media-hub (3.0.0) UNRELEASED; urgency=medium |
7 | + |
8 | + * Refactor client-facing interfaces to pull out explicit dependency on hybris-based media layer. |
9 | + |
10 | + -- Thomas Voss <thomas.voss@canonical.com> Tue, 11 Nov 2014 17:15:52 +0100 |
11 | + |
12 | media-hub (2.0.0+15.04.20150303-0ubuntu2) vivid; urgency=medium |
13 | |
14 | * debian/control: |
15 | @@ -17,14 +22,6 @@ |
16 | |
17 | -- CI Train Bot <ci-train-bot@canonical.com> Tue, 03 Mar 2015 22:56:52 +0000 |
18 | |
19 | -======= |
20 | -media-hub (3.0.0) UNRELEASED; urgency=medium |
21 | - |
22 | - * Refactor client-facing interfaces to pull out explicit dependency on hybris-based media layer. |
23 | - |
24 | - -- Thomas Voss <thomas.voss@canonical.com> Tue, 11 Nov 2014 17:15:52 +0100 |
25 | - |
26 | ->>>>>>> MERGE-SOURCE |
27 | media-hub (2.0.0+15.04.20150120-0ubuntu1) vivid; urgency=low |
28 | |
29 | [ Jim Hodapp ] |
30 | |
31 | === modified file 'debian/control' |
32 | --- debian/control 2015-03-16 15:38:27 +0000 |
33 | +++ debian/control 2015-03-16 15:38:27 +0000 |
34 | @@ -39,14 +39,8 @@ |
35 | Section: libdevel |
36 | Architecture: any |
37 | Multi-Arch: same |
38 | -<<<<<<< TREE |
39 | -Depends: libmedia-hub-common2 (= ${binary:Version}), |
40 | - libmedia-hub-client2 (= ${binary:Version}), |
41 | -======= |
42 | -Pre-Depends: dpkg (>= 1.15.6~) |
43 | Depends: libmedia-hub-common3 (= ${binary:Version}), |
44 | libmedia-hub-client3 (= ${binary:Version}), |
45 | ->>>>>>> MERGE-SOURCE |
46 | ${misc:Depends}, |
47 | libproperties-cpp-dev, |
48 | Suggests: libmedia-hub-doc |
49 | |
50 | === modified file 'include/core/media/player.h' |
51 | --- include/core/media/player.h 2015-03-16 15:38:27 +0000 |
52 | +++ include/core/media/player.h 2015-03-16 15:38:27 +0000 |
53 | @@ -168,6 +168,7 @@ |
54 | virtual core::Property<Lifetime>& lifetime() = 0; |
55 | |
56 | virtual const core::Signal<int64_t>& seeked_to() const = 0; |
57 | + virtual const core::Signal<void>& about_to_finish() const = 0; |
58 | virtual const core::Signal<void>& end_of_stream() const = 0; |
59 | virtual core::Signal<PlaybackStatus>& playback_status_changed() = 0; |
60 | virtual const core::Signal<video::Dimensions>& video_dimension_changed() const = 0; |
61 | |
62 | === modified file 'src/core/media/CMakeLists.txt' |
63 | --- src/core/media/CMakeLists.txt 2015-03-16 15:38:27 +0000 |
64 | +++ src/core/media/CMakeLists.txt 2015-03-16 15:38:27 +0000 |
65 | @@ -11,7 +11,7 @@ |
66 | |
67 | message(STATUS ${MEDIA_HUB_HEADERS}) |
68 | |
69 | -add_subdirectory(call-monitor) |
70 | +add_subdirectory(telephony) |
71 | |
72 | add_library( |
73 | media-hub-common SHARED |
74 | @@ -87,10 +87,14 @@ |
75 | ${MPRIS_HEADERS} |
76 | |
77 | client_death_observer.cpp |
78 | - hybris_client_death_observer.cpp |
79 | + hashed_keyed_player_store.cpp |
80 | + hybris_client_death_observer.cpp |
81 | cover_art_resolver.cpp |
82 | engine.cpp |
83 | |
84 | + apparmor/context.cpp |
85 | + apparmor/ubuntu.cpp |
86 | + |
87 | audio/pulse_audio_output_observer.cpp |
88 | audio/ostream_reporter.cpp |
89 | audio/output_observer.cpp |
90 | |
91 | === added directory 'src/core/media/apparmor' |
92 | === removed file 'src/core/media/apparmor.h' |
93 | --- src/core/media/apparmor.h 2014-09-01 07:49:45 +0000 |
94 | +++ src/core/media/apparmor.h 1970-01-01 00:00:00 +0000 |
95 | @@ -1,93 +0,0 @@ |
96 | -/* |
97 | - * Copyright (C) 2013-2014 Canonical Ltd |
98 | - * |
99 | - * This program is free software: you can redistribute it and/or modify |
100 | - * it under the terms of the GNU Lesser General Public License version 3 as |
101 | - * published by the Free Software Foundation. |
102 | - * |
103 | - * This program is distributed in the hope that it will be useful, |
104 | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
105 | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
106 | - * GNU Lesser General Public License for more details. |
107 | - * |
108 | - * You should have received a copy of the GNU Lesser General Public License |
109 | - * along with this program. If not, see <http://www.gnu.org/licenses/>. |
110 | - * |
111 | - * Author: Jim Hodapp <jim.hodapp@canonical.com> |
112 | - */ |
113 | - |
114 | -#ifndef APPARMOR_DBUS_H_ |
115 | -#define APPARMOR_DBUS_H_ |
116 | - |
117 | -#include <core/dbus/macros.h> |
118 | -#include <core/dbus/object.h> |
119 | -#include <core/dbus/service.h> |
120 | - |
121 | -#include <string> |
122 | -#include <chrono> |
123 | - |
124 | -// TODO(tvoss): This really should live in trust-store, providing a straightforward |
125 | -// way for parties involved in managing trust relationships to query peers' apparmor |
126 | -// profiles. Please see https://bugs.launchpad.net/trust-store/+bug/1350736 for the |
127 | -// related bug |
128 | -namespace org |
129 | -{ |
130 | -namespace freedesktop |
131 | -{ |
132 | -namespace dbus |
133 | -{ |
134 | -struct DBus |
135 | -{ |
136 | - static const std::string& name() |
137 | - { |
138 | - static const std::string s = "org.freedesktop.DBus"; |
139 | - return s; |
140 | - } |
141 | - |
142 | - // Gets the AppArmor confinement string associated with the unique connection name. If |
143 | - // D-Bus is not performing AppArmor mediation, the |
144 | - // org.freedesktop.DBus.Error.AppArmorSecurityContextUnknown error is returned. |
145 | - DBUS_CPP_METHOD_DEF(GetConnectionAppArmorSecurityContext, DBus) |
146 | - |
147 | - struct Stub |
148 | - { |
149 | - // Creates a new stub instance for the given object to access |
150 | - // DBus functionality. |
151 | - Stub(const core::dbus::Object::Ptr& object) : object{object} |
152 | - { |
153 | - } |
154 | - |
155 | - // Creates a new stub instance for the given bus connection |
156 | - Stub(const core::dbus::Bus::Ptr& bus) |
157 | - : object |
158 | - { |
159 | - core::dbus::Service::use_service<org::freedesktop::dbus::DBus>(bus) |
160 | - ->object_for_path(core::dbus::types::ObjectPath{"/org/freedesktop/DBus"}) |
161 | - } |
162 | - { |
163 | - } |
164 | - |
165 | - // Gets the AppArmor confinement string associated with the unique connection name. If |
166 | - // D-Bus is not performing AppArmor mediation, the |
167 | - // org.freedesktop.DBus.Error.AppArmorSecurityContextUnknown error is returned. |
168 | - // |
169 | - // Invokes the given handler on completion. |
170 | - void get_connection_app_armor_security_async( |
171 | - const std::string& name, |
172 | - std::function<void(const std::string&)> handler) |
173 | - { |
174 | - object->invoke_method_asynchronously_with_callback<GetConnectionAppArmorSecurityContext, std::string>( |
175 | - [handler](const core::dbus::Result<std::string>& result) |
176 | - { |
177 | - if (not result.is_error()) handler(result.value()); |
178 | - }, name); |
179 | - } |
180 | - |
181 | - core::dbus::Object::Ptr object; |
182 | - }; |
183 | -}; |
184 | -} |
185 | -} |
186 | -} |
187 | - |
188 | -#endif // APPARMOR_DBUS_H_ |
189 | |
190 | === added file 'src/core/media/apparmor/context.cpp' |
191 | --- src/core/media/apparmor/context.cpp 1970-01-01 00:00:00 +0000 |
192 | +++ src/core/media/apparmor/context.cpp 2015-03-16 15:38:27 +0000 |
193 | @@ -0,0 +1,34 @@ |
194 | +/* |
195 | + * Copyright © 2014 Canonical Ltd. |
196 | + * |
197 | + * This program is free software: you can redistribute it and/or modify it |
198 | + * under the terms of the GNU Lesser General Public License version 3, |
199 | + * as published by the Free Software Foundation. |
200 | + * |
201 | + * This program is distributed in the hope that it will be useful, |
202 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
203 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
204 | + * GNU Lesser General Public License for more details. |
205 | + * |
206 | + * You should have received a copy of the GNU Lesser General Public License |
207 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
208 | + * |
209 | + * Authored by: Thomas Voß <thomas.voss@canonical.com> |
210 | + */ |
211 | + |
212 | +#include <core/media/apparmor/context.h> |
213 | + |
214 | +namespace apparmor = core::ubuntu::media::apparmor; |
215 | + |
216 | +apparmor::Context::Context(const std::string& name) : name{name} |
217 | +{ |
218 | + if (name.empty()) throw std::runtime_error |
219 | + { |
220 | + "apparmor::Context cannot be created for empty name." |
221 | + }; |
222 | +} |
223 | + |
224 | +const std::string& apparmor::Context::str() const |
225 | +{ |
226 | + return name; |
227 | +} |
228 | |
229 | === added file 'src/core/media/apparmor/context.h' |
230 | --- src/core/media/apparmor/context.h 1970-01-01 00:00:00 +0000 |
231 | +++ src/core/media/apparmor/context.h 2015-03-16 15:38:27 +0000 |
232 | @@ -0,0 +1,51 @@ |
233 | +/* |
234 | + * Copyright © 2014 Canonical Ltd. |
235 | + * |
236 | + * This program is free software: you can redistribute it and/or modify it |
237 | + * under the terms of the GNU Lesser General Public License version 3, |
238 | + * as published by the Free Software Foundation. |
239 | + * |
240 | + * This program is distributed in the hope that it will be useful, |
241 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
242 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
243 | + * GNU Lesser General Public License for more details. |
244 | + * |
245 | + * You should have received a copy of the GNU Lesser General Public License |
246 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
247 | + * |
248 | + * Authored by: Thomas Voß <thomas.voss@canonical.com> |
249 | + */ |
250 | +#ifndef CORE_UBUNTU_MEDIA_APPARMOR_AUTHENTICATOR_H_ |
251 | +#define CORE_UBUNTU_MEDIA_APPARMOR_AUTHENTICATOR_H_ |
252 | + |
253 | +#include <memory> |
254 | +#include <string> |
255 | + |
256 | +namespace core |
257 | +{ |
258 | +namespace ubuntu |
259 | +{ |
260 | +namespace media |
261 | +{ |
262 | +namespace apparmor |
263 | +{ |
264 | +// Models an apparmor context name, and provides convenience functionality |
265 | +// on top of it. |
266 | +class Context |
267 | +{ |
268 | +public: |
269 | + // Constructs a new Context instance for the given raw name. |
270 | + // Throws std::logic_error for empty names. |
271 | + explicit Context(const std::string& name); |
272 | + virtual ~Context() = default; |
273 | + // Returns the raw string describing the context. |
274 | + const std::string& str() const; |
275 | + |
276 | +private: |
277 | + const std::string name; |
278 | +}; |
279 | +} |
280 | +} |
281 | +} |
282 | +} |
283 | +#endif // CORE_UBUNTU_MEDIA_APPARMOR_AUTHENTICATOR_H_ |
284 | |
285 | === added file 'src/core/media/apparmor/dbus.h' |
286 | --- src/core/media/apparmor/dbus.h 1970-01-01 00:00:00 +0000 |
287 | +++ src/core/media/apparmor/dbus.h 2015-03-16 15:38:27 +0000 |
288 | @@ -0,0 +1,94 @@ |
289 | +/* |
290 | + * Copyright (C) 2013-2014 Canonical Ltd |
291 | + * |
292 | + * This program is free software: you can redistribute it and/or modify |
293 | + * it under the terms of the GNU Lesser General Public License version 3 as |
294 | + * published by the Free Software Foundation. |
295 | + * |
296 | + * This program is distributed in the hope that it will be useful, |
297 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
298 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
299 | + * GNU Lesser General Public License for more details. |
300 | + * |
301 | + * You should have received a copy of the GNU Lesser General Public License |
302 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
303 | + * |
304 | + * Author: Jim Hodapp <jim.hodapp@canonical.com> |
305 | + */ |
306 | + |
307 | +#ifndef CORE_UBUNTU_MEDIA_APPARMOR_DBUS_H_ |
308 | +#define CORE_UBUNTU_MEDIA_APPARMOR_DBUS_H_ |
309 | + |
310 | +#include <core/dbus/bus.h> |
311 | +#include <core/dbus/macros.h> |
312 | +#include <core/dbus/object.h> |
313 | +#include <core/dbus/service.h> |
314 | + |
315 | +#include <string> |
316 | +#include <chrono> |
317 | + |
318 | +// TODO(tvoss): This really should live in trust-store, providing a straightforward |
319 | +// way for parties involved in managing trust relationships to query peers' apparmor |
320 | +// profiles. Please see https://bugs.launchpad.net/trust-store/+bug/1350736 for the |
321 | +// related bug |
322 | +namespace org |
323 | +{ |
324 | +namespace freedesktop |
325 | +{ |
326 | +namespace dbus |
327 | +{ |
328 | +struct DBus |
329 | +{ |
330 | + static const std::string& name() |
331 | + { |
332 | + static const std::string s = "org.freedesktop.DBus"; |
333 | + return s; |
334 | + } |
335 | + |
336 | + // Gets the AppArmor confinement string associated with the unique connection name. If |
337 | + // D-Bus is not performing AppArmor mediation, the |
338 | + // org.freedesktop.DBus.Error.AppArmorSecurityContextUnknown error is returned. |
339 | + DBUS_CPP_METHOD_DEF(GetConnectionAppArmorSecurityContext, DBus) |
340 | + |
341 | + struct Stub |
342 | + { |
343 | + // Creates a new stub instance for the given object to access |
344 | + // DBus functionality. |
345 | + Stub(const core::dbus::Object::Ptr& object) : object{object} |
346 | + { |
347 | + } |
348 | + |
349 | + // Creates a new stub instance for the given bus connection |
350 | + Stub(const core::dbus::Bus::Ptr& bus) |
351 | + : object |
352 | + { |
353 | + core::dbus::Service::use_service<org::freedesktop::dbus::DBus>(bus) |
354 | + ->object_for_path(core::dbus::types::ObjectPath{"/org/freedesktop/DBus"}) |
355 | + } |
356 | + { |
357 | + } |
358 | + |
359 | + // Gets the AppArmor confinement string associated with the unique connection name. If |
360 | + // D-Bus is not performing AppArmor mediation, the |
361 | + // org.freedesktop.DBus.Error.AppArmorSecurityContextUnknown error is returned. |
362 | + // |
363 | + // Invokes the given handler on completion. |
364 | + void get_connection_app_armor_security_async( |
365 | + const std::string& name, |
366 | + std::function<void(const std::string&)> handler) |
367 | + { |
368 | + object->invoke_method_asynchronously_with_callback<GetConnectionAppArmorSecurityContext, std::string>( |
369 | + [handler](const core::dbus::Result<std::string>& result) |
370 | + { |
371 | + if (not result.is_error()) handler(result.value()); |
372 | + }, name); |
373 | + } |
374 | + |
375 | + core::dbus::Object::Ptr object; |
376 | + }; |
377 | +}; |
378 | +} |
379 | +} |
380 | +} |
381 | + |
382 | +#endif // CORE_UBUNTU_MEDIA_APPARMOR_DBUS_H_ |
383 | |
384 | === added file 'src/core/media/apparmor/ubuntu.cpp' |
385 | --- src/core/media/apparmor/ubuntu.cpp 1970-01-01 00:00:00 +0000 |
386 | +++ src/core/media/apparmor/ubuntu.cpp 2015-03-16 15:38:27 +0000 |
387 | @@ -0,0 +1,198 @@ |
388 | +/* |
389 | + * Copyright © 2014 Canonical Ltd. |
390 | + * |
391 | + * This program is free software: you can redistribute it and/or modify it |
392 | + * under the terms of the GNU Lesser General Public License version 3, |
393 | + * as published by the Free Software Foundation. |
394 | + * |
395 | + * This program is distributed in the hope that it will be useful, |
396 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
397 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
398 | + * GNU Lesser General Public License for more details. |
399 | + * |
400 | + * You should have received a copy of the GNU Lesser General Public License |
401 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
402 | + * |
403 | + * Authored by: Thomas Voß <thomas.voss@canonical.com> |
404 | + */ |
405 | + |
406 | +#include <core/media/apparmor/ubuntu.h> |
407 | + |
408 | +#include <core/media/external_services.h> |
409 | + |
410 | +#include <iostream> |
411 | +#include <regex> |
412 | + |
413 | +namespace apparmor = core::ubuntu::media::apparmor; |
414 | +namespace media = core::ubuntu::media; |
415 | +namespace ubuntu = apparmor::ubuntu; |
416 | + |
417 | +namespace |
418 | +{ |
419 | +struct Uri |
420 | +{ |
421 | + std::string scheme; |
422 | + std::string authority; |
423 | + std::string path; |
424 | + std::string query; |
425 | + std::string fragment; |
426 | +}; |
427 | + |
428 | +// Poor mans version of a uri parser. |
429 | +// See https://tools.ietf.org/html/rfc3986#appendix-B |
430 | +Uri parse_uri(const std::string& s) |
431 | +{ |
432 | + // Indices into the regex match go here. |
433 | + struct Index |
434 | + { |
435 | + const std::size_t scheme{2}; |
436 | + const std::size_t authority{4}; |
437 | + const std::size_t path{5}; |
438 | + const std::size_t query{7}; |
439 | + const std::size_t fragment{9}; |
440 | + } static index; |
441 | + |
442 | + static const std::regex regex{R"delim(^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)delim"}; |
443 | + std::smatch match; |
444 | + |
445 | + if (not std::regex_match(s, match, regex)) throw std::runtime_error |
446 | + { |
447 | + "Not a valid URI: " + s |
448 | + }; |
449 | + |
450 | + return Uri |
451 | + { |
452 | + match.str(index.scheme), |
453 | + match.str(index.authority), |
454 | + match.str(index.path), |
455 | + match.str(index.query), |
456 | + match.str(index.fragment) |
457 | + }; |
458 | +} |
459 | + |
460 | +static constexpr std::size_t index_package{1}; |
461 | +static constexpr std::size_t index_app{2}; |
462 | + |
463 | +// Returns true if the context name is a valid Ubuntu app id. |
464 | +// If it is, out is populated with the package and app name. |
465 | +bool process_context_name(const std::string& s, std::smatch& out) |
466 | +{ |
467 | + // See https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId. |
468 | + static const std::regex short_re{"(.*)_(.*)"}; |
469 | + static const std::regex full_re{"(.*)_(.*)_(.*)"}; |
470 | + |
471 | + if (std::regex_match(s, out, full_re)) |
472 | + return true; |
473 | + |
474 | + if (std::regex_match(s, out, short_re)) |
475 | + return true; |
476 | + |
477 | + return false; |
478 | +} |
479 | +} |
480 | + |
481 | +apparmor::ubuntu::Context::Context(const std::string& name) |
482 | + : apparmor::Context{name}, |
483 | + unconfined_{str() == ubuntu::unconfined}, |
484 | + has_package_name_{process_context_name(str(), match_)} |
485 | +{ |
486 | + if (not is_unconfined() && not has_package_name()) throw std::logic_error |
487 | + { |
488 | + "apparmor::ubuntu::Context: Invalid profile name " + str() |
489 | + }; |
490 | +} |
491 | + |
492 | +bool apparmor::ubuntu::Context::is_unconfined() const |
493 | +{ |
494 | + return unconfined_; |
495 | +} |
496 | + |
497 | +bool apparmor::ubuntu::Context::has_package_name() const |
498 | +{ |
499 | + return has_package_name_; |
500 | +} |
501 | + |
502 | + |
503 | +std::string apparmor::ubuntu::Context::package_name() const |
504 | +{ |
505 | + return std::string{match_[index_package]}; |
506 | +} |
507 | + |
508 | +apparmor::ubuntu::DBusDaemonRequestContextResolver::DBusDaemonRequestContextResolver(const core::dbus::Bus::Ptr& bus) : dbus_daemon{bus} |
509 | +{ |
510 | +} |
511 | + |
512 | +void apparmor::ubuntu::DBusDaemonRequestContextResolver::resolve_context_for_dbus_name_async( |
513 | + const std::string& name, |
514 | + apparmor::ubuntu::RequestContextResolver::ResolveCallback cb) |
515 | +{ |
516 | + dbus_daemon.get_connection_app_armor_security_async(name, [cb](const std::string& context_name) |
517 | + { |
518 | + cb(apparmor::ubuntu::Context{context_name}); |
519 | + }); |
520 | +} |
521 | + |
522 | +apparmor::ubuntu::RequestAuthenticator::Result apparmor::ubuntu::ExistingAuthenticator::authenticate_open_uri_request(const apparmor::ubuntu::Context& context, const std::string& uri) |
523 | +{ |
524 | + if (context.is_unconfined()) |
525 | + return Result{true, "Client allowed access since it's unconfined"}; |
526 | + |
527 | + Uri parsed_uri = parse_uri(uri); |
528 | + |
529 | + // All confined apps can access their own files |
530 | + if (parsed_uri.path.find(std::string(".local/share/" + context.package_name() + "/")) != std::string::npos || |
531 | + parsed_uri.path.find(std::string(".cache/" + context.package_name() + "/")) != std::string::npos) |
532 | + { |
533 | + return Result |
534 | + { |
535 | + true, |
536 | + "Client can access content in ~/.local/share/" + context.package_name() + " or ~/.cache/" + context.package_name() |
537 | + }; |
538 | + } |
539 | + else if (parsed_uri.path.find(std::string("opt/click.ubuntu.com/")) != std::string::npos && |
540 | + parsed_uri.path.find(context.package_name()) != std::string::npos) |
541 | + { |
542 | + return Result{true, "Client can access content in own opt directory"}; |
543 | + } |
544 | + else if ((parsed_uri.path.find(std::string("/system/media/audio/ui/")) != std::string::npos || |
545 | + parsed_uri.path.find(std::string("/android/system/media/audio/ui/")) != std::string::npos) && |
546 | + context.package_name() == "com.ubuntu.camera") |
547 | + { |
548 | + return Result{true, "Camera app can access ui sounds"}; |
549 | + } |
550 | + |
551 | + // TODO: Check if the trust store previously allowed direct access to uri |
552 | + |
553 | + // Check in ~/Music and ~/Videos |
554 | + // TODO: when the trust store lands, check it to see if this app can access the dirs and |
555 | + // then remove the explicit whitelist of the music-app, and gallery-app |
556 | + else if ((context.package_name() == "com.ubuntu.music" || context.package_name() == "com.ubuntu.gallery") && |
557 | + (parsed_uri.path.find(std::string("Music/")) != std::string::npos || |
558 | + parsed_uri.path.find(std::string("Videos/")) != std::string::npos || |
559 | + parsed_uri.path.find(std::string("/media")) != std::string::npos)) |
560 | + { |
561 | + return Result{true, "Client can access content in ~/Music or ~/Videos"}; |
562 | + } |
563 | + else if (parsed_uri.path.find(std::string("/usr/share/sounds")) != std::string::npos) |
564 | + { |
565 | + return Result{true, "Client can access content in /usr/share/sounds"}; |
566 | + } |
567 | + else if (parsed_uri.scheme == "http" || parsed_uri.scheme == "rtsp") |
568 | + { |
569 | + return Result{true, "Client can access streaming content"}; |
570 | + } |
571 | + |
572 | + return Result{false, "Client is not allowed to access: " + uri}; |
573 | +} |
574 | + |
575 | +// Returns the platform-default implementation of RequestContextResolver. |
576 | +apparmor::ubuntu::RequestContextResolver::Ptr apparmor::ubuntu::make_platform_default_request_context_resolver(media::helper::ExternalServices& es) |
577 | +{ |
578 | + return std::make_shared<apparmor::ubuntu::DBusDaemonRequestContextResolver>(es.session); |
579 | +} |
580 | + |
581 | +// Returns the platform-default implementation of RequestAuthenticator. |
582 | +apparmor::ubuntu::RequestAuthenticator::Ptr apparmor::ubuntu::make_platform_default_request_authenticator() |
583 | +{ |
584 | + return std::make_shared<apparmor::ubuntu::ExistingAuthenticator>(); |
585 | +} |
586 | |
587 | === added file 'src/core/media/apparmor/ubuntu.h' |
588 | --- src/core/media/apparmor/ubuntu.h 1970-01-01 00:00:00 +0000 |
589 | +++ src/core/media/apparmor/ubuntu.h 2015-03-16 15:38:27 +0000 |
590 | @@ -0,0 +1,167 @@ |
591 | +/* |
592 | + * Copyright © 2014 Canonical Ltd. |
593 | + * |
594 | + * This program is free software: you can redistribute it and/or modify it |
595 | + * under the terms of the GNU Lesser General Public License version 3, |
596 | + * as published by the Free Software Foundation. |
597 | + * |
598 | + * This program is distributed in the hope that it will be useful, |
599 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
600 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
601 | + * GNU Lesser General Public License for more details. |
602 | + * |
603 | + * You should have received a copy of the GNU Lesser General Public License |
604 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
605 | + * |
606 | + * Authored by: Thomas Voß <thomas.voss@canonical.com> |
607 | + */ |
608 | +#ifndef CORE_UBUNTU_MEDIA_APPARMOR_UBUNTU_H_ |
609 | +#define CORE_UBUNTU_MEDIA_APPARMOR_UBUNTU_H_ |
610 | + |
611 | +#include <core/media/apparmor/context.h> |
612 | +#include <core/media/apparmor/dbus.h> |
613 | + |
614 | +#include <functional> |
615 | +#include <memory> |
616 | +#include <regex> |
617 | +#include <string> |
618 | +#include <vector> |
619 | + |
620 | +namespace core |
621 | +{ |
622 | +namespace dbus |
623 | +{ |
624 | +class Bus; |
625 | +} |
626 | + |
627 | +namespace ubuntu |
628 | +{ |
629 | +namespace media |
630 | +{ |
631 | +namespace helper |
632 | +{ |
633 | +struct ExternalServices; |
634 | +} |
635 | +namespace apparmor |
636 | +{ |
637 | +// Collects Ubuntu-specific apparmor conventions, e.g., format |
638 | +// of short and full package names as well as convenience functionality |
639 | +// to inspect apparmor::Context instances. |
640 | +namespace ubuntu |
641 | +{ |
642 | +// The unconfined profile, unconditionally trusted |
643 | +// by the system. |
644 | +static constexpr const char* unconfined |
645 | +{ |
646 | + "unconfined" |
647 | +}; |
648 | + |
649 | +class Context : public apparmor::Context |
650 | +{ |
651 | +public: |
652 | + // Constructs a new Context instance for the given raw name. |
653 | + // Throws std::logic_error for empty names or for names not |
654 | + // complying to Ubuntu conventions. |
655 | + Context(const std::string& name); |
656 | + |
657 | + // Returns true iff the context is unconfined. |
658 | + virtual bool is_unconfined() const; |
659 | + |
660 | + // Returns true iff the context contains a package name. |
661 | + virtual bool has_package_name() const; |
662 | + |
663 | + // Returns the package name or throws if no package name can be found. |
664 | + virtual std::string package_name() const; |
665 | + |
666 | +private: |
667 | + std::smatch match_; |
668 | + const bool unconfined_; |
669 | + const bool has_package_name_; |
670 | +}; |
671 | + |
672 | +// Abstracts query for the apparmor context of an incoming request |
673 | +class RequestContextResolver |
674 | +{ |
675 | +public: |
676 | + // To save us some typing. |
677 | + typedef std::shared_ptr<RequestContextResolver> Ptr; |
678 | + |
679 | + // Callback for resolve context operations. |
680 | + typedef std::function<void(const Context&)> ResolveCallback; |
681 | + |
682 | + // Resolves the given name (of a dbus participant) to its apparmor context, |
683 | + // invoking the callback whenever a result is available. |
684 | + virtual void resolve_context_for_dbus_name_async(const std::string& name, ResolveCallback cb) = 0; |
685 | + |
686 | +protected: |
687 | + RequestContextResolver() = default; |
688 | + RequestContextResolver(const RequestContextResolver&) = delete; |
689 | + virtual ~RequestContextResolver() = default; |
690 | + RequestContextResolver& operator=(const RequestContextResolver&) = delete; |
691 | +}; |
692 | + |
693 | +// An implementation of RequestContextResolver that queries the dbus |
694 | +// daemon to resolve the apparmor context. |
695 | +class DBusDaemonRequestContextResolver : public RequestContextResolver |
696 | +{ |
697 | +public: |
698 | + // To save us some typing. |
699 | + typedef std::shared_ptr<DBusDaemonRequestContextResolver> Ptr; |
700 | + |
701 | + // Constructs a new instance for the given bus connection. |
702 | + DBusDaemonRequestContextResolver(const core::dbus::Bus::Ptr &); |
703 | + |
704 | + // From RequestContextResolver |
705 | + void resolve_context_for_dbus_name_async(const std::string& name, ResolveCallback) override; |
706 | + |
707 | +private: |
708 | + org::freedesktop::dbus::DBus::Stub dbus_daemon; |
709 | +}; |
710 | + |
711 | +// Abstracts an apparmor-based authentication of |
712 | +// incoming requests from clients. |
713 | +class RequestAuthenticator |
714 | +{ |
715 | +public: |
716 | + // To save us some typing. |
717 | + typedef std::shared_ptr<RequestAuthenticator> Ptr; |
718 | + |
719 | + // Return type of an authentication call. |
720 | + typedef std::tuple |
721 | + < |
722 | + bool, // True if authenticated, false if not. |
723 | + std::string // Reason for the result. |
724 | + > Result; |
725 | + |
726 | + virtual ~RequestAuthenticator() = default; |
727 | + |
728 | + // Returns true iff the client identified by the given apparmor::Context is allowed |
729 | + // to access the given uri, false otherwise. |
730 | + virtual Result authenticate_open_uri_request(const Context&, const std::string& uri) = 0; |
731 | + |
732 | +protected: |
733 | + RequestAuthenticator() = default; |
734 | + RequestAuthenticator(const RequestAuthenticator&) = default; |
735 | + RequestAuthenticator& operator=(const RequestAuthenticator&) = default; |
736 | +}; |
737 | + |
738 | +// Takes the existing logic and exposes it as an implementation |
739 | +// of the RequestAuthenticator interface. |
740 | +struct ExistingAuthenticator : public RequestAuthenticator |
741 | +{ |
742 | + ExistingAuthenticator() = default; |
743 | + // From RequestAuthenticator |
744 | + Result authenticate_open_uri_request(const Context&, const std::string& uri) override; |
745 | +}; |
746 | + |
747 | +// Returns the platform-default implementation of RequestContextResolver. |
748 | +RequestContextResolver::Ptr make_platform_default_request_context_resolver(helper::ExternalServices& es); |
749 | +// Returns the platform-default implementation of RequestAuthenticator. |
750 | +RequestAuthenticator::Ptr make_platform_default_request_authenticator(); |
751 | +} |
752 | +} |
753 | +} |
754 | +} |
755 | +} |
756 | + |
757 | +#endif // CORE_UBUNTU_MEDIA_APPARMOR_UBUNTU_H_ |
758 | |
759 | === modified file 'src/core/media/gstreamer/engine.cpp' |
760 | --- src/core/media/gstreamer/engine.cpp 2015-03-16 15:38:27 +0000 |
761 | +++ src/core/media/gstreamer/engine.cpp 2015-03-16 15:38:27 +0000 |
762 | @@ -355,7 +355,7 @@ |
763 | if (result) |
764 | { |
765 | d->state = media::Engine::State::playing; |
766 | - cout << "play" << endl; |
767 | + cout << __PRETTY_FUNCTION__ << endl; |
768 | d->playback_status_changed(media::Player::PlaybackStatus::playing); |
769 | } |
770 | |
771 | @@ -373,7 +373,7 @@ |
772 | if (result) |
773 | { |
774 | d->state = media::Engine::State::stopped; |
775 | - cout << "stop" << endl; |
776 | + cout << __PRETTY_FUNCTION__ << endl; |
777 | d->playback_status_changed(media::Player::PlaybackStatus::stopped); |
778 | } |
779 | |
780 | @@ -387,7 +387,7 @@ |
781 | if (result) |
782 | { |
783 | d->state = media::Engine::State::paused; |
784 | - cout << "pause" << endl; |
785 | + cout << __PRETTY_FUNCTION__ << endl; |
786 | d->playback_status_changed(media::Player::PlaybackStatus::paused); |
787 | } |
788 | |
789 | |
790 | === modified file 'src/core/media/gstreamer/playbin.cpp' |
791 | --- src/core/media/gstreamer/playbin.cpp 2015-03-16 15:38:27 +0000 |
792 | +++ src/core/media/gstreamer/playbin.cpp 2015-03-16 15:38:27 +0000 |
793 | @@ -97,11 +97,15 @@ |
794 | this, |
795 | std::placeholders::_1))), |
796 | is_seeking(false), |
797 | - player_lifetime(media::Player::Lifetime::normal) |
798 | + previous_position(0), |
799 | + player_lifetime(media::Player::Lifetime::normal), |
800 | + is_eos(false) |
801 | { |
802 | if (!pipeline) |
803 | throw std::runtime_error("Could not create pipeline for playbin."); |
804 | |
805 | + is_eos = false; |
806 | + |
807 | // Add audio and/or video sink elements depending on environment variables |
808 | // being set or not set |
809 | setup_pipeline_for_audio_video(); |
810 | @@ -199,6 +203,7 @@ |
811 | } |
812 | break; |
813 | case GST_MESSAGE_EOS: |
814 | + is_eos = true; |
815 | signals.on_end_of_stream(); |
816 | default: |
817 | break; |
818 | @@ -334,6 +339,17 @@ |
819 | int64_t pos = 0; |
820 | gst_element_query_position (pipeline, GST_FORMAT_TIME, &pos); |
821 | |
822 | + // This prevents a 0 position from being reported to the app which happens while seeking. |
823 | + // This is covering over a GStreamer issue |
824 | + if ((static_cast<uint64_t>(pos) < duration()) && is_seeking && pos == 0) |
825 | + { |
826 | + return previous_position; |
827 | + } |
828 | + |
829 | + // Save the current position to use just in case it's needed the next time position is |
830 | + // requested |
831 | + previous_position = static_cast<uint64_t>(pos); |
832 | + |
833 | // FIXME: this should be int64_t, but dbus-cpp doesn't seem to handle it correctly |
834 | return static_cast<uint64_t>(pos); |
835 | } |
836 | @@ -358,7 +374,7 @@ |
837 | file_type = MEDIA_FILE_TYPE_VIDEO; |
838 | else if (is_audio_file(uri)) |
839 | file_type = MEDIA_FILE_TYPE_AUDIO; |
840 | - |
841 | + |
842 | request_headers = headers; |
843 | } |
844 | |
845 | @@ -366,7 +382,7 @@ |
846 | { |
847 | if (source == NULL || request_headers.empty()) |
848 | return; |
849 | - |
850 | + |
851 | if (request_headers.find("Cookie") != request_headers.end()) { |
852 | if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), |
853 | "cookies") != NULL) { |
854 | @@ -375,7 +391,7 @@ |
855 | g_strfreev(cookies); |
856 | } |
857 | } |
858 | - |
859 | + |
860 | if (request_headers.find("User-Agent") != request_headers.end()) { |
861 | if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), |
862 | "user-agent") != NULL) { |
863 | @@ -427,8 +443,9 @@ |
864 | } |
865 | |
866 | // We only should query the pipeline if we actually succeeded in |
867 | - // setting the requested state. |
868 | - if (result && new_state == GST_STATE_PLAYING) |
869 | + // setting the requested state. Also don't send on_video_dimensions_changed |
870 | + // signal during EOS. |
871 | + if (result && new_state == GST_STATE_PLAYING && !is_eos) |
872 | { |
873 | // Get the video height/width from the video sink |
874 | try |
875 | |
876 | === modified file 'src/core/media/gstreamer/playbin.h' |
877 | --- src/core/media/gstreamer/playbin.h 2015-03-16 15:38:27 +0000 |
878 | +++ src/core/media/gstreamer/playbin.h 2015-03-16 15:38:27 +0000 |
879 | @@ -112,8 +112,10 @@ |
880 | GstElement* video_sink; |
881 | core::Connection on_new_message_connection; |
882 | bool is_seeking; |
883 | + mutable uint64_t previous_position; |
884 | core::ubuntu::media::Player::HeadersType request_headers; |
885 | core::ubuntu::media::Player::Lifetime player_lifetime; |
886 | + bool is_eos; |
887 | struct |
888 | { |
889 | core::Signal<void> about_to_finish; |
890 | |
891 | === added file 'src/core/media/hashed_keyed_player_store.cpp' |
892 | --- src/core/media/hashed_keyed_player_store.cpp 1970-01-01 00:00:00 +0000 |
893 | +++ src/core/media/hashed_keyed_player_store.cpp 2015-03-16 15:38:27 +0000 |
894 | @@ -0,0 +1,80 @@ |
895 | +/* |
896 | + * Copyright © 2014 Canonical Ltd. |
897 | + * |
898 | + * This program is free software: you can redistribute it and/or modify it |
899 | + * under the terms of the GNU Lesser General Public License version 3, |
900 | + * as published by the Free Software Foundation. |
901 | + * |
902 | + * This program is distributed in the hope that it will be useful, |
903 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
904 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
905 | + * GNU Lesser General Public License for more details. |
906 | + * |
907 | + * You should have received a copy of the GNU Lesser General Public License |
908 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
909 | + * |
910 | + * Authored by: Thomas Voß <thomas.voss@canonical.com> |
911 | + */ |
912 | + |
913 | +#include <core/media/hashed_keyed_player_store.h> |
914 | + |
915 | +namespace media = core::ubuntu::media; |
916 | + |
917 | +media::HashedKeyedPlayerStore::HashedKeyedPlayerStore() |
918 | +{ |
919 | +} |
920 | + |
921 | +const core::Property<std::shared_ptr<media::Player>>& media::HashedKeyedPlayerStore::current_player() const |
922 | +{ |
923 | + return prop_current_player; |
924 | +} |
925 | + |
926 | +bool media::HashedKeyedPlayerStore::has_player_for_key(const media::Player::PlayerKey& key) const |
927 | +{ |
928 | + std::lock_guard<std::mutex> lg{guard}; |
929 | + return map.count(key) > 0; |
930 | +} |
931 | + |
932 | +std::shared_ptr<media::Player> media::HashedKeyedPlayerStore::player_for_key(const media::Player::PlayerKey& key) const |
933 | +{ |
934 | + std::lock_guard<std::mutex> lg{guard}; |
935 | + auto it = map.find(key); |
936 | + |
937 | + if (it == map.end()) throw std::out_of_range |
938 | + { |
939 | + "HashedKeyedPlayerStore::player_for_key: No player known for " + std::to_string(key) |
940 | + }; |
941 | + |
942 | + return it->second; |
943 | +} |
944 | + |
945 | +void media::HashedKeyedPlayerStore::enumerate_players(const media::KeyedPlayerStore::PlayerEnumerator& enumerator) const |
946 | +{ |
947 | + std::lock_guard<std::mutex> lg{guard}; |
948 | + for (const auto& pair : map) |
949 | + enumerator(pair.first, pair.second); |
950 | +} |
951 | + |
952 | +void media::HashedKeyedPlayerStore::add_player_for_key(const media::Player::PlayerKey& key, const std::shared_ptr<media::Player>& player) |
953 | +{ |
954 | + std::lock_guard<std::mutex> lg{guard}; |
955 | + map[key] = player; |
956 | +} |
957 | + |
958 | +void media::HashedKeyedPlayerStore::remove_player_for_key(const media::Player::PlayerKey& key) |
959 | +{ |
960 | + std::lock_guard<std::mutex> lg{guard}; |
961 | + auto it = map.find(key); |
962 | + if (it != map.end()) |
963 | + { |
964 | + if (prop_current_player == it->second) |
965 | + prop_current_player = nullptr; |
966 | + |
967 | + map.erase(it); |
968 | + } |
969 | +} |
970 | + |
971 | +void media::HashedKeyedPlayerStore::set_current_player_for_key(const media::Player::PlayerKey& key) |
972 | +{ |
973 | + prop_current_player = player_for_key(key); |
974 | +} |
975 | |
976 | === added file 'src/core/media/hashed_keyed_player_store.h' |
977 | --- src/core/media/hashed_keyed_player_store.h 1970-01-01 00:00:00 +0000 |
978 | +++ src/core/media/hashed_keyed_player_store.h 2015-03-16 15:38:27 +0000 |
979 | @@ -0,0 +1,76 @@ |
980 | +/* |
981 | + * Copyright © 2014 Canonical Ltd. |
982 | + * |
983 | + * This program is free software: you can redistribute it and/or modify it |
984 | + * under the terms of the GNU Lesser General Public License version 3, |
985 | + * as published by the Free Software Foundation. |
986 | + * |
987 | + * This program is distributed in the hope that it will be useful, |
988 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
989 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
990 | + * GNU Lesser General Public License for more details. |
991 | + * |
992 | + * You should have received a copy of the GNU Lesser General Public License |
993 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
994 | + * |
995 | + * Authored by: Thomas Voß <thomas.voss@canonical.com> |
996 | + */ |
997 | + |
998 | +#ifndef CORE_UBUNTU_MEDIA_HASHED_KEYED_PLAYER_STORE_H_ |
999 | +#define CORE_UBUNTU_MEDIA_HASHED_KEYED_PLAYER_STORE_H_ |
1000 | + |
1001 | +#include <core/media/keyed_player_store.h> |
1002 | + |
1003 | +#include <mutex> |
1004 | +#include <unordered_map> |
1005 | + |
1006 | +namespace core |
1007 | +{ |
1008 | +namespace ubuntu |
1009 | +{ |
1010 | +namespace media |
1011 | +{ |
1012 | +// Implements KeyedPlayerStore using a std::unordered_map. |
1013 | +class HashedKeyedPlayerStore : public KeyedPlayerStore |
1014 | +{ |
1015 | +public: |
1016 | + HashedKeyedPlayerStore(); |
1017 | + // We keep track of the "current" player, that is, the one |
1018 | + // that has been created most recently, or has been explicitly foregrounded, or has been enabled for |
1019 | + // background playback. We provide a getable/observable access to that designated instance. |
1020 | + const core::Property<std::shared_ptr<media::Player>>& current_player() const override; |
1021 | + |
1022 | + // We keep track of all known player sessions here and render them accessible via |
1023 | + // the key. All of these functions are thread-safe but not reentrant. |
1024 | + // Returns true iff a player is known for the given key. |
1025 | + bool has_player_for_key(const Player::PlayerKey& key) const override; |
1026 | + |
1027 | + // Returns the player for the given key or throws std::out_of_range if no player is known |
1028 | + // for the given key. |
1029 | + // Throws std::out_of_range if no player is known for the key. |
1030 | + std::shared_ptr<Player> player_for_key(const Player::PlayerKey& key) const override; |
1031 | + |
1032 | + // Enumerates all known players and invokes the given enumerator for each |
1033 | + // (key, player) pair. |
1034 | + void enumerate_players(const PlayerEnumerator& enumerator) const override; |
1035 | + |
1036 | + // Adds the given player with the given key. |
1037 | + void add_player_for_key(const Player::PlayerKey& key, const std::shared_ptr<Player>& player) override; |
1038 | + |
1039 | + // Removes the player for the given key, and unsets it if it is the current one. |
1040 | + void remove_player_for_key(const Player::PlayerKey& key) override; |
1041 | + |
1042 | + // Makes the player known under the given key current. |
1043 | + // Throws std::out_of_range if no player is known for the key. |
1044 | + void set_current_player_for_key(const Player::PlayerKey& key) override; |
1045 | + |
1046 | +private: |
1047 | + core::Property<std::shared_ptr<Player>> prop_current_player; |
1048 | + mutable std::mutex guard; |
1049 | + std::unordered_map<Player::PlayerKey, std::shared_ptr<Player>> map; |
1050 | +}; |
1051 | +} |
1052 | +} |
1053 | +} |
1054 | + |
1055 | +#endif // CORE_UBUNTU_MEDIA_KEYED_PLAYER_STORE_H_ |
1056 | |
1057 | === added file 'src/core/media/keyed_player_store.cpp' |
1058 | === added file 'src/core/media/keyed_player_store.h' |
1059 | --- src/core/media/keyed_player_store.h 1970-01-01 00:00:00 +0000 |
1060 | +++ src/core/media/keyed_player_store.h 2015-03-16 15:38:27 +0000 |
1061 | @@ -0,0 +1,83 @@ |
1062 | +/* |
1063 | + * Copyright © 2014 Canonical Ltd. |
1064 | + * |
1065 | + * This program is free software: you can redistribute it and/or modify it |
1066 | + * under the terms of the GNU Lesser General Public License version 3, |
1067 | + * as published by the Free Software Foundation. |
1068 | + * |
1069 | + * This program is distributed in the hope that it will be useful, |
1070 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1071 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1072 | + * GNU Lesser General Public License for more details. |
1073 | + * |
1074 | + * You should have received a copy of the GNU Lesser General Public License |
1075 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1076 | + * |
1077 | + * Authored by: Thomas Voß <thomas.voss@canonical.com> |
1078 | + */ |
1079 | + |
1080 | +#ifndef CORE_UBUNTU_MEDIA_KEYED_PLAYER_STORE_H_ |
1081 | +#define CORE_UBUNTU_MEDIA_KEYED_PLAYER_STORE_H_ |
1082 | + |
1083 | +#include <core/media/player.h> |
1084 | + |
1085 | +#include <core/property.h> |
1086 | + |
1087 | +#include <functional> |
1088 | +#include <memory> |
1089 | + |
1090 | +namespace core |
1091 | +{ |
1092 | +namespace ubuntu |
1093 | +{ |
1094 | +namespace media |
1095 | +{ |
1096 | +// An interface abstracting keyed lookups of known Player instances. |
1097 | +class KeyedPlayerStore |
1098 | +{ |
1099 | +public: |
1100 | + // Save us some typing. |
1101 | + typedef std::shared_ptr<KeyedPlayerStore> Ptr; |
1102 | + // Functor for enumerating all known (key, player) pairs. |
1103 | + typedef std::function |
1104 | + < |
1105 | + void( |
1106 | + // The key of the player. |
1107 | + const Player::PlayerKey&, |
1108 | + // The actual player instance. |
1109 | + const std::shared_ptr<Player>& |
1110 | + ) |
1111 | + > PlayerEnumerator; |
1112 | + // We keep track of the "current" player, that is, the one |
1113 | + // that has been created most recently and provide a getable/observable |
1114 | + // access to that designated instance. |
1115 | + virtual const core::Property<std::shared_ptr<Player>>& current_player() const = 0; |
1116 | + |
1117 | + // We keep track of all known player sessions here and render them accessible via |
1118 | + // the key. All of these functions are thread-safe but not reentrant. |
1119 | + // Returns true iff a player is known for the given key. |
1120 | + virtual bool has_player_for_key(const Player::PlayerKey& key) const = 0; |
1121 | + // Returns the player for the given key or throws std::out_of_range if no player is known |
1122 | + // for the given key. |
1123 | + virtual std::shared_ptr<Player> player_for_key(const Player::PlayerKey& key) const = 0; |
1124 | + // Enumerates all known players and invokes the given enumerator for each |
1125 | + // (key, player) pair. |
1126 | + virtual void enumerate_players(const PlayerEnumerator& enumerator) const = 0; |
1127 | + // Adds the given player with the given key. |
1128 | + virtual void add_player_for_key(const Player::PlayerKey& key, const std::shared_ptr<Player>& player) = 0; |
1129 | + // Removes the player for the given key, and unsets it if it is the current one. |
1130 | + virtual void remove_player_for_key(const Player::PlayerKey& key) = 0; |
1131 | + // Makes the player known under the given key current. |
1132 | + virtual void set_current_player_for_key(const Player::PlayerKey& key) = 0; |
1133 | + |
1134 | +protected: |
1135 | + KeyedPlayerStore() = default; |
1136 | + KeyedPlayerStore(const KeyedPlayerStore&) = delete; |
1137 | + virtual ~KeyedPlayerStore() = default; |
1138 | + KeyedPlayerStore& operator=(const KeyedPlayerStore&) = delete; |
1139 | +}; |
1140 | +} |
1141 | +} |
1142 | +} |
1143 | + |
1144 | +#endif // CORE_UBUNTU_MEDIA_KEYED_PLAYER_STORE_H_ |
1145 | |
1146 | === modified file 'src/core/media/mpris/player.h' |
1147 | --- src/core/media/mpris/player.h 2015-03-16 15:38:27 +0000 |
1148 | +++ src/core/media/mpris/player.h 2015-03-16 15:38:27 +0000 |
1149 | @@ -136,6 +136,7 @@ |
1150 | struct Signals |
1151 | { |
1152 | DBUS_CPP_SIGNAL_DEF(Seeked, Player, std::int64_t) |
1153 | + DBUS_CPP_SIGNAL_DEF(AboutToFinish, Player, void) |
1154 | DBUS_CPP_SIGNAL_DEF(EndOfStream, Player, void) |
1155 | DBUS_CPP_SIGNAL_DEF(PlaybackStatusChanged, Player, core::ubuntu::media::Player::PlaybackStatus) |
1156 | DBUS_CPP_SIGNAL_DEF(VideoDimensionChanged, Player, core::ubuntu::media::video::Dimensions) |
1157 | @@ -246,6 +247,7 @@ |
1158 | signals |
1159 | { |
1160 | configuration.object->template get_signal<Signals::Seeked>(), |
1161 | + configuration.object->template get_signal<Signals::AboutToFinish>(), |
1162 | configuration.object->template get_signal<Signals::EndOfStream>(), |
1163 | configuration.object->template get_signal<Signals::PlaybackStatusChanged>(), |
1164 | configuration.object->template get_signal<Signals::VideoDimensionChanged>(), |
1165 | @@ -373,6 +375,7 @@ |
1166 | struct |
1167 | { |
1168 | typename core::dbus::Signal<Signals::Seeked, Signals::Seeked::ArgumentType>::Ptr seeked_to; |
1169 | + typename core::dbus::Signal<Signals::AboutToFinish, Signals::AboutToFinish::ArgumentType>::Ptr about_to_finish; |
1170 | typename core::dbus::Signal<Signals::EndOfStream, Signals::EndOfStream::ArgumentType>::Ptr end_of_stream; |
1171 | typename core::dbus::Signal<Signals::PlaybackStatusChanged, Signals::PlaybackStatusChanged::ArgumentType>::Ptr playback_status_changed; |
1172 | typename core::dbus::Signal<Signals::VideoDimensionChanged, Signals::VideoDimensionChanged::ArgumentType>::Ptr video_dimension_changed; |
1173 | |
1174 | === added file 'src/core/media/null_track_list.h' |
1175 | --- src/core/media/null_track_list.h 1970-01-01 00:00:00 +0000 |
1176 | +++ src/core/media/null_track_list.h 2015-03-16 15:38:27 +0000 |
1177 | @@ -0,0 +1,114 @@ |
1178 | +/* |
1179 | + * |
1180 | + * This program is free software: you can redistribute it and/or modify it |
1181 | + * under the terms of the GNU Lesser General Public License version 3, |
1182 | + * as published by the Free Software Foundation. |
1183 | + * |
1184 | + * This program is distributed in the hope that it will be useful, |
1185 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1186 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1187 | + * GNU Lesser General Public License for more details. |
1188 | + * |
1189 | + * You should have received a copy of the GNU Lesser General Public License |
1190 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1191 | + * |
1192 | + * Authored by: Thomas Voß <thomas.voss@canonical.com> |
1193 | + */ |
1194 | + |
1195 | +#ifndef CORE_MEDIA_NULL_TRACK_LIST_H_ |
1196 | +#define CORE_MEDIA_NULL_TRACK_LIST_H_ |
1197 | + |
1198 | +#include <core/media/track.h> |
1199 | +#include <core/media/track_list.h> |
1200 | + |
1201 | +namespace core |
1202 | +{ |
1203 | +namespace ubuntu |
1204 | +{ |
1205 | +namespace media |
1206 | +{ |
1207 | +// A helper type to replace the playlist implementation below. |
1208 | +// Please note that this type is only a temporary manner. Ideally, |
1209 | +// the actual implementation should be injected as a dependency from the |
1210 | +// outside. |
1211 | +struct NullTrackList : public media::TrackList |
1212 | +{ |
1213 | + NullTrackList() = default; |
1214 | + |
1215 | + bool has_next() |
1216 | + { |
1217 | + return false; |
1218 | + } |
1219 | + |
1220 | + media::Track::Id next() |
1221 | + { |
1222 | + return media::Track::Id{}; |
1223 | + } |
1224 | + |
1225 | + media::Track::UriType query_uri_for_track(const media::Track::Id&) |
1226 | + { |
1227 | + return media::Track::UriType{}; |
1228 | + } |
1229 | + |
1230 | + const core::Property<bool>& can_edit_tracks() const override |
1231 | + { |
1232 | + return props_and_sigs.can_edit_tracks; |
1233 | + } |
1234 | + |
1235 | + const core::Property<Container>& tracks() const override |
1236 | + { |
1237 | + return props_and_sigs.tracks; |
1238 | + } |
1239 | + |
1240 | + virtual media::Track::MetaData query_meta_data_for_track(const media::Track::Id&) override |
1241 | + { |
1242 | + return media::Track::MetaData{}; |
1243 | + } |
1244 | + |
1245 | + void add_track_with_uri_at(const media::Track::UriType&, const media::Track::Id&, bool) override |
1246 | + { |
1247 | + } |
1248 | + |
1249 | + void remove_track(const media::Track::Id&) override |
1250 | + { |
1251 | + } |
1252 | + |
1253 | + void go_to(const media::Track::Id&) override |
1254 | + { |
1255 | + } |
1256 | + |
1257 | + const core::Signal<void>& on_track_list_replaced() const override |
1258 | + { |
1259 | + return props_and_sigs.on_track_list_replaced; |
1260 | + } |
1261 | + |
1262 | + const core::Signal<media::Track::Id>& on_track_added() const override |
1263 | + { |
1264 | + return props_and_sigs.on_track_added; |
1265 | + } |
1266 | + |
1267 | + const core::Signal<media::Track::Id>& on_track_removed() const override |
1268 | + { |
1269 | + return props_and_sigs.on_track_removed; |
1270 | + } |
1271 | + |
1272 | + const core::Signal<media::Track::Id>& on_track_changed() const override |
1273 | + { |
1274 | + return props_and_sigs.on_track_changed; |
1275 | + } |
1276 | + |
1277 | + struct |
1278 | + { |
1279 | + core::Property<bool> can_edit_tracks; |
1280 | + core::Property<TrackList::Container> tracks; |
1281 | + core::Signal<void> on_track_list_replaced; |
1282 | + core::Signal<media::Track::Id> on_track_added; |
1283 | + core::Signal<media::Track::Id> on_track_removed; |
1284 | + core::Signal<media::Track::Id> on_track_changed; |
1285 | + } props_and_sigs; |
1286 | +}; |
1287 | +} |
1288 | +} |
1289 | +} |
1290 | + |
1291 | +#endif // CORE_MEDIA_NULL_TRACK_LIST_H_ |
1292 | |
1293 | === modified file 'src/core/media/player.cpp' |
1294 | --- src/core/media/player.cpp 2015-03-16 15:38:27 +0000 |
1295 | +++ src/core/media/player.cpp 2015-03-16 15:38:27 +0000 |
1296 | @@ -31,7 +31,6 @@ |
1297 | { |
1298 | static const media::Player::Configuration config |
1299 | { |
1300 | - std::string{""}, |
1301 | 0, |
1302 | nullptr, |
1303 | nullptr |
1304 | |
1305 | === modified file 'src/core/media/player_configuration.h' |
1306 | --- src/core/media/player_configuration.h 2014-09-09 10:28:32 +0000 |
1307 | +++ src/core/media/player_configuration.h 2015-03-16 15:38:27 +0000 |
1308 | @@ -27,9 +27,6 @@ |
1309 | // to the implementation in a way that is opaque to the client. |
1310 | struct core::ubuntu::media::Player::Configuration |
1311 | { |
1312 | - // An identifier that is helpful in referencing the player instance |
1313 | - // across multiple services. |
1314 | - std::string identity; |
1315 | // Unique key for identifying the session. |
1316 | core::ubuntu::media::Player::PlayerKey key; |
1317 | // The bus connection to expose objects on. |
1318 | |
1319 | === modified file 'src/core/media/player_implementation.cpp' |
1320 | --- src/core/media/player_implementation.cpp 2015-03-16 15:38:27 +0000 |
1321 | +++ src/core/media/player_implementation.cpp 2015-03-16 15:38:27 +0000 |
1322 | @@ -23,6 +23,7 @@ |
1323 | |
1324 | #include "client_death_observer.h" |
1325 | #include "engine.h" |
1326 | +#include "null_track_list.h" |
1327 | #include "track_list_implementation.h" |
1328 | |
1329 | #include "gstreamer/engine.h" |
1330 | @@ -39,7 +40,8 @@ |
1331 | |
1332 | using namespace std; |
1333 | |
1334 | -struct media::PlayerImplementation::Private : |
1335 | +template<typename Parent> |
1336 | +struct media::PlayerImplementation<Parent>::Private : |
1337 | public std::enable_shared_from_this<Private> |
1338 | { |
1339 | enum class wakelock_clear_t |
1340 | @@ -50,30 +52,18 @@ |
1341 | WAKELOCK_CLEAR_INVALID |
1342 | }; |
1343 | |
1344 | - Private(PlayerImplementation* parent, const media::PlayerImplementation::Configuration& config) |
1345 | + Private(PlayerImplementation* parent, const media::PlayerImplementation<Parent>::Configuration& config) |
1346 | : parent(parent), |
1347 | config(config), |
1348 | display_state_lock(config.power_state_controller->display_state_lock()), |
1349 | system_state_lock(config.power_state_controller->system_state_lock()), |
1350 | engine(std::make_shared<gstreamer::Engine>()), |
1351 | - track_list( |
1352 | - new media::TrackListImplementation( |
1353 | - config.session->path().as_string() + "/TrackList", |
1354 | - engine->meta_data_extractor())), |
1355 | + track_list(std::make_shared<NullTrackList>()), |
1356 | system_wakelock_count(0), |
1357 | display_wakelock_count(0), |
1358 | previous_state(Engine::State::stopped), |
1359 | engine_state_change_connection(engine->state().changed().connect(make_state_change_handler())) |
1360 | { |
1361 | - config.client_death_observer->register_for_death_notifications_with_key(config.key); |
1362 | - config.client_death_observer->on_client_with_key_died().connect([this](const media::Player::PlayerKey& died) |
1363 | - { |
1364 | - if (died != this->config.key) |
1365 | - return; |
1366 | - |
1367 | - on_client_died(); |
1368 | - }); |
1369 | - |
1370 | // Poor man's logging of release/acquire events. |
1371 | display_state_lock->acquired().connect([](media::power::DisplayState state) |
1372 | { |
1373 | @@ -207,7 +197,7 @@ |
1374 | if (--system_wakelock_count == 0) |
1375 | { |
1376 | std::cout << "Clearing system wakelock." << std::endl; |
1377 | - system_state_lock->request_release(media::power::SystemState::active); |
1378 | + system_state_lock->request_release(media::power::SystemState::active); |
1379 | } |
1380 | break; |
1381 | case wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY: |
1382 | @@ -257,7 +247,7 @@ |
1383 | // the execution of the functor may surpass the lifetime of this Private |
1384 | // object instance. By keeping a weak_ptr to the private object instance |
1385 | // we can check if the object is dead before calling methods on it |
1386 | - std::weak_ptr<Private> weak_self{shared_from_this()}; |
1387 | + std::weak_ptr<Private> weak_self{this->shared_from_this()}; |
1388 | auto wakelock_type = current_wakelock_type(); |
1389 | return [weak_self, wakelock_type] { |
1390 | if (auto self = weak_self.lock()) |
1391 | @@ -271,14 +261,14 @@ |
1392 | } |
1393 | |
1394 | // Our link back to our parent. |
1395 | - media::PlayerImplementation* parent; |
1396 | + media::PlayerImplementation<Parent>* parent; |
1397 | // We just store the parameters passed on construction. |
1398 | - media::PlayerImplementation::Configuration config; |
1399 | + media::PlayerImplementation<Parent>::Configuration config; |
1400 | media::power::StateController::Lock<media::power::DisplayState>::Ptr display_state_lock; |
1401 | media::power::StateController::Lock<media::power::SystemState>::Ptr system_state_lock; |
1402 | |
1403 | std::shared_ptr<Engine> engine; |
1404 | - std::shared_ptr<TrackListImplementation> track_list; |
1405 | + std::shared_ptr<media::NullTrackList> track_list; |
1406 | std::atomic<int> system_wakelock_count; |
1407 | std::atomic<int> display_wakelock_count; |
1408 | Engine::State previous_state; |
1409 | @@ -286,36 +276,29 @@ |
1410 | core::Connection engine_state_change_connection; |
1411 | }; |
1412 | |
1413 | -media::PlayerImplementation::PlayerImplementation(const media::PlayerImplementation::Configuration& config) |
1414 | - : media::PlayerSkeleton |
1415 | - { |
1416 | - media::PlayerSkeleton::Configuration |
1417 | - { |
1418 | - config.bus, |
1419 | - config.session, |
1420 | - config.identity |
1421 | - } |
1422 | - }, |
1423 | +template<typename Parent> |
1424 | +media::PlayerImplementation<Parent>::PlayerImplementation(const media::PlayerImplementation<Parent>::Configuration& config) |
1425 | + : Parent{config.parent}, |
1426 | d{std::make_shared<Private>(this, config)} |
1427 | { |
1428 | // Initialize default values for Player interface properties |
1429 | - can_play().set(true); |
1430 | - can_pause().set(true); |
1431 | - can_seek().set(true); |
1432 | - can_go_previous().set(true); |
1433 | - can_go_next().set(true); |
1434 | - is_video_source().set(false); |
1435 | - is_audio_source().set(false); |
1436 | - is_shuffle().set(true); |
1437 | - playback_rate().set(1.f); |
1438 | - playback_status().set(Player::PlaybackStatus::null); |
1439 | - loop_status().set(Player::LoopStatus::none); |
1440 | - position().set(0); |
1441 | - duration().set(0); |
1442 | - audio_stream_role().set(Player::AudioStreamRole::multimedia); |
1443 | + Parent::can_play().set(true); |
1444 | + Parent::can_pause().set(true); |
1445 | + Parent::can_seek().set(true); |
1446 | + Parent::can_go_previous().set(true); |
1447 | + Parent::can_go_next().set(true); |
1448 | + Parent::is_video_source().set(false); |
1449 | + Parent::is_audio_source().set(false); |
1450 | + Parent::is_shuffle().set(true); |
1451 | + Parent::playback_rate().set(1.f); |
1452 | + Parent::playback_status().set(Player::PlaybackStatus::null); |
1453 | + Parent::loop_status().set(Player::LoopStatus::none); |
1454 | + Parent::position().set(0); |
1455 | + Parent::duration().set(0); |
1456 | + Parent::audio_stream_role().set(Player::AudioStreamRole::multimedia); |
1457 | d->engine->audio_stream_role().set(Player::AudioStreamRole::multimedia); |
1458 | - orientation().set(Player::Orientation::rotate0); |
1459 | - lifetime().set(Player::Lifetime::normal); |
1460 | + Parent::orientation().set(Player::Orientation::rotate0); |
1461 | + Parent::lifetime().set(Player::Lifetime::normal); |
1462 | d->engine->lifetime().set(Player::Lifetime::normal); |
1463 | |
1464 | // Make sure that the Position property gets updated from the Engine |
1465 | @@ -324,7 +307,7 @@ |
1466 | { |
1467 | return d->engine->position().get(); |
1468 | }; |
1469 | - position().install(position_getter); |
1470 | + Parent::position().install(position_getter); |
1471 | |
1472 | // Make sure that the Duration property gets updated from the Engine |
1473 | // every time the client requests duration |
1474 | @@ -332,23 +315,23 @@ |
1475 | { |
1476 | return d->engine->duration().get(); |
1477 | }; |
1478 | - duration().install(duration_getter); |
1479 | + Parent::duration().install(duration_getter); |
1480 | |
1481 | std::function<bool()> video_type_getter = [this]() |
1482 | { |
1483 | return d->engine->is_video_source().get(); |
1484 | }; |
1485 | - is_video_source().install(video_type_getter); |
1486 | + Parent::is_video_source().install(video_type_getter); |
1487 | |
1488 | std::function<bool()> audio_type_getter = [this]() |
1489 | { |
1490 | return d->engine->is_audio_source().get(); |
1491 | }; |
1492 | - is_audio_source().install(audio_type_getter); |
1493 | + Parent::is_audio_source().install(audio_type_getter); |
1494 | |
1495 | // Make sure that the audio_stream_role property gets updated on the Engine side |
1496 | // whenever the client side sets the role |
1497 | - audio_stream_role().changed().connect([this](media::Player::AudioStreamRole new_role) |
1498 | + Parent::audio_stream_role().changed().connect([this](media::Player::AudioStreamRole new_role) |
1499 | { |
1500 | d->engine->audio_stream_role().set(new_role); |
1501 | }); |
1502 | @@ -357,16 +340,18 @@ |
1503 | // update the Player's cached value |
1504 | d->engine->orientation().changed().connect([this](const Player::Orientation& o) |
1505 | { |
1506 | - orientation().set(o); |
1507 | + Parent::orientation().set(o); |
1508 | }); |
1509 | |
1510 | - lifetime().changed().connect([this](media::Player::Lifetime lifetime) |
1511 | + Parent::lifetime().changed().connect([this](media::Player::Lifetime lifetime) |
1512 | { |
1513 | d->engine->lifetime().set(lifetime); |
1514 | }); |
1515 | |
1516 | d->engine->about_to_finish_signal().connect([this]() |
1517 | { |
1518 | + Parent::about_to_finish()(); |
1519 | + |
1520 | if (d->track_list->has_next()) |
1521 | { |
1522 | Track::UriType uri = d->track_list->query_uri_for_track(d->track_list->next()); |
1523 | @@ -386,31 +371,52 @@ |
1524 | |
1525 | d->engine->seeked_to_signal().connect([this](uint64_t value) |
1526 | { |
1527 | - seeked_to()(value); |
1528 | + Parent::seeked_to()(value); |
1529 | }); |
1530 | |
1531 | d->engine->end_of_stream_signal().connect([this]() |
1532 | { |
1533 | - end_of_stream()(); |
1534 | + Parent::end_of_stream()(); |
1535 | }); |
1536 | |
1537 | d->engine->playback_status_changed_signal().connect([this](const Player::PlaybackStatus& status) |
1538 | { |
1539 | - playback_status_changed()(status); |
1540 | + Parent::playback_status_changed()(status); |
1541 | }); |
1542 | |
1543 | d->engine->video_dimension_changed_signal().connect([this](const media::video::Dimensions& dimensions) |
1544 | { |
1545 | - video_dimension_changed()(dimensions); |
1546 | + Parent::video_dimension_changed()(dimensions); |
1547 | }); |
1548 | |
1549 | d->engine->error_signal().connect([this](const Player::Error& e) |
1550 | { |
1551 | - error()(e); |
1552 | + Parent::error()(e); |
1553 | + }); |
1554 | + |
1555 | + // Everything is setup, we now subscribe to death notifications. |
1556 | + std::weak_ptr<Private> wp{d}; |
1557 | + |
1558 | + d->config.client_death_observer->register_for_death_notifications_with_key(config.key); |
1559 | + d->config.client_death_observer->on_client_with_key_died().connect([wp](const media::Player::PlayerKey& died) |
1560 | + { |
1561 | + if (auto sp = wp.lock()) |
1562 | + { |
1563 | + if (died != sp->config.key) |
1564 | + return; |
1565 | + |
1566 | + static const std::chrono::milliseconds timeout{1000}; |
1567 | + media::timeout(timeout.count(), true, [wp]() |
1568 | + { |
1569 | + if (auto sp = wp.lock()) |
1570 | + sp->on_client_died(); |
1571 | + }); |
1572 | + } |
1573 | }); |
1574 | } |
1575 | |
1576 | -media::PlayerImplementation::~PlayerImplementation() |
1577 | +template<typename Parent> |
1578 | +media::PlayerImplementation<Parent>::~PlayerImplementation() |
1579 | { |
1580 | // Install null getters as these properties may be destroyed |
1581 | // after the engine has been destroyed since they are owned by the |
1582 | @@ -419,84 +425,101 @@ |
1583 | { |
1584 | return static_cast<uint64_t>(0); |
1585 | }; |
1586 | - position().install(position_getter); |
1587 | + Parent::position().install(position_getter); |
1588 | |
1589 | std::function<uint64_t()> duration_getter = [this]() |
1590 | { |
1591 | return static_cast<uint64_t>(0); |
1592 | }; |
1593 | - duration().install(duration_getter); |
1594 | + Parent::duration().install(duration_getter); |
1595 | |
1596 | std::function<bool()> video_type_getter = [this]() |
1597 | { |
1598 | return false; |
1599 | }; |
1600 | - is_video_source().install(video_type_getter); |
1601 | + Parent::is_video_source().install(video_type_getter); |
1602 | |
1603 | std::function<bool()> audio_type_getter = [this]() |
1604 | { |
1605 | return false; |
1606 | }; |
1607 | - is_audio_source().install(audio_type_getter); |
1608 | + Parent::is_audio_source().install(audio_type_getter); |
1609 | } |
1610 | |
1611 | -std::shared_ptr<media::TrackList> media::PlayerImplementation::track_list() |
1612 | +template<typename Parent> |
1613 | +std::shared_ptr<media::TrackList> media::PlayerImplementation<Parent>::track_list() |
1614 | { |
1615 | return d->track_list; |
1616 | } |
1617 | |
1618 | // TODO: Convert this to be a property instead of sync call |
1619 | -media::Player::PlayerKey media::PlayerImplementation::key() const |
1620 | +template<typename Parent> |
1621 | +media::Player::PlayerKey media::PlayerImplementation<Parent>::key() const |
1622 | { |
1623 | return d->config.key; |
1624 | } |
1625 | |
1626 | -media::video::Sink::Ptr media::PlayerImplementation::create_gl_texture_video_sink(std::uint32_t texture_id) |
1627 | +template<typename Parent> |
1628 | +media::video::Sink::Ptr media::PlayerImplementation<Parent>::create_gl_texture_video_sink(std::uint32_t texture_id) |
1629 | { |
1630 | d->engine->create_video_sink(texture_id); |
1631 | return media::video::Sink::Ptr{}; |
1632 | } |
1633 | |
1634 | -bool media::PlayerImplementation::open_uri(const Track::UriType& uri) |
1635 | +template<typename Parent> |
1636 | +bool media::PlayerImplementation<Parent>::open_uri(const Track::UriType& uri) |
1637 | { |
1638 | return d->engine->open_resource_for_uri(uri); |
1639 | } |
1640 | |
1641 | -bool media::PlayerImplementation::open_uri(const Track::UriType& uri, const Player::HeadersType& headers) |
1642 | +template<typename Parent> |
1643 | +bool media::PlayerImplementation<Parent>::open_uri(const Track::UriType& uri, const Player::HeadersType& headers) |
1644 | { |
1645 | return d->engine->open_resource_for_uri(uri, headers); |
1646 | } |
1647 | |
1648 | -void media::PlayerImplementation::next() |
1649 | -{ |
1650 | -} |
1651 | - |
1652 | -void media::PlayerImplementation::previous() |
1653 | -{ |
1654 | -} |
1655 | - |
1656 | -void media::PlayerImplementation::play() |
1657 | +template<typename Parent> |
1658 | +void media::PlayerImplementation<Parent>::next() |
1659 | +{ |
1660 | +} |
1661 | + |
1662 | +template<typename Parent> |
1663 | +void media::PlayerImplementation<Parent>::previous() |
1664 | +{ |
1665 | +} |
1666 | + |
1667 | +template<typename Parent> |
1668 | +void media::PlayerImplementation<Parent>::play() |
1669 | { |
1670 | d->engine->play(); |
1671 | } |
1672 | |
1673 | -void media::PlayerImplementation::pause() |
1674 | +template<typename Parent> |
1675 | +void media::PlayerImplementation<Parent>::pause() |
1676 | { |
1677 | d->engine->pause(); |
1678 | } |
1679 | |
1680 | -void media::PlayerImplementation::stop() |
1681 | +template<typename Parent> |
1682 | +void media::PlayerImplementation<Parent>::stop() |
1683 | { |
1684 | std::cout << __PRETTY_FUNCTION__ << std::endl; |
1685 | d->engine->stop(); |
1686 | } |
1687 | |
1688 | -void media::PlayerImplementation::seek_to(const std::chrono::microseconds& ms) |
1689 | +template<typename Parent> |
1690 | +void media::PlayerImplementation<Parent>::seek_to(const std::chrono::microseconds& ms) |
1691 | { |
1692 | d->engine->seek_to(ms); |
1693 | } |
1694 | |
1695 | -const core::Signal<>& media::PlayerImplementation::on_client_disconnected() const |
1696 | +template<typename Parent> |
1697 | +const core::Signal<>& media::PlayerImplementation<Parent>::on_client_disconnected() const |
1698 | { |
1699 | return d->on_client_disconnected; |
1700 | } |
1701 | + |
1702 | +#include <core/media/player_skeleton.h> |
1703 | + |
1704 | +// For linking purposes, we have to make sure that we have all symbols included within the dso. |
1705 | +template class media::PlayerImplementation<media::PlayerSkeleton>; |
1706 | |
1707 | === modified file 'src/core/media/player_implementation.h' |
1708 | --- src/core/media/player_implementation.h 2015-03-16 15:38:27 +0000 |
1709 | +++ src/core/media/player_implementation.h 2015-03-16 15:38:27 +0000 |
1710 | @@ -19,8 +19,9 @@ |
1711 | #ifndef CORE_UBUNTU_MEDIA_PLAYER_IMPLEMENTATION_H_ |
1712 | #define CORE_UBUNTU_MEDIA_PLAYER_IMPLEMENTATION_H_ |
1713 | |
1714 | -#include "player_skeleton.h" |
1715 | +#include <core/media/player.h> |
1716 | |
1717 | +#include "apparmor/ubuntu.h" |
1718 | #include "client_death_observer.h" |
1719 | #include "power/state_controller.h" |
1720 | |
1721 | @@ -35,18 +36,17 @@ |
1722 | class Engine; |
1723 | class Service; |
1724 | |
1725 | -class PlayerImplementation : public PlayerSkeleton |
1726 | +template<typename Parent> |
1727 | +class PlayerImplementation : public Parent |
1728 | { |
1729 | public: |
1730 | // All creation time arguments go here |
1731 | struct Configuration |
1732 | { |
1733 | - std::string identity; |
1734 | - std::shared_ptr<core::dbus::Bus> bus; |
1735 | - std::shared_ptr<core::dbus::Object> session; |
1736 | - std::shared_ptr<Service> service; |
1737 | - PlayerKey key; |
1738 | - |
1739 | + // All creation time configuration options of the Parent class. |
1740 | + typename Parent::Configuration parent; |
1741 | + // The unique key identifying the player instance. |
1742 | + Player::PlayerKey key; |
1743 | // Functional dependencies |
1744 | ClientDeathObserver::Ptr client_death_observer; |
1745 | power::StateController::Ptr power_state_controller; |
1746 | @@ -56,7 +56,7 @@ |
1747 | ~PlayerImplementation(); |
1748 | |
1749 | virtual std::shared_ptr<TrackList> track_list(); |
1750 | - virtual PlayerKey key() const; |
1751 | + virtual Player::PlayerKey key() const; |
1752 | |
1753 | virtual video::Sink::Ptr create_gl_texture_video_sink(std::uint32_t texture_id); |
1754 | |
1755 | |
1756 | === modified file 'src/core/media/player_skeleton.cpp' |
1757 | --- src/core/media/player_skeleton.cpp 2015-03-16 15:38:27 +0000 |
1758 | +++ src/core/media/player_skeleton.cpp 2015-03-16 15:38:27 +0000 |
1759 | @@ -17,15 +17,16 @@ |
1760 | * Jim Hodapp <jim.hodapp@canonical.com> |
1761 | */ |
1762 | |
1763 | -#include "apparmor.h" |
1764 | #include "codec.h" |
1765 | #include "engine.h" |
1766 | +#include "external_services.h" |
1767 | #include "player_skeleton.h" |
1768 | #include "player_traits.h" |
1769 | #include "property_stub.h" |
1770 | #include "the_session_bus.h" |
1771 | #include "xesam.h" |
1772 | |
1773 | +#include "apparmor/ubuntu.h" |
1774 | #include "mpris/media_player2.h" |
1775 | #include "mpris/metadata.h" |
1776 | #include "mpris/player.h" |
1777 | @@ -44,19 +45,20 @@ |
1778 | struct media::PlayerSkeleton::Private |
1779 | { |
1780 | Private(media::PlayerSkeleton* player, |
1781 | - const std::string& identity, |
1782 | const std::shared_ptr<core::dbus::Bus>& bus, |
1783 | - const std::shared_ptr<core::dbus::Object>& session) |
1784 | + const std::shared_ptr<core::dbus::Object>& session, |
1785 | + const apparmor::ubuntu::RequestContextResolver::Ptr& request_context_resolver, |
1786 | + const apparmor::ubuntu::RequestAuthenticator::Ptr& request_authenticator) |
1787 | : impl(player), |
1788 | - identity(identity), |
1789 | bus(bus), |
1790 | object(session), |
1791 | - apparmor_session(nullptr), |
1792 | - dbus_stub{bus}, |
1793 | + request_context_resolver{request_context_resolver}, |
1794 | + request_authenticator{request_authenticator}, |
1795 | skeleton{mpris::Player::Skeleton::Configuration{bus, session, mpris::Player::Skeleton::Configuration::Defaults{}}}, |
1796 | signals |
1797 | { |
1798 | skeleton.signals.seeked_to, |
1799 | + skeleton.signals.about_to_finish, |
1800 | skeleton.signals.end_of_stream, |
1801 | skeleton.signals.playback_status_changed, |
1802 | skeleton.signals.video_dimension_changed, |
1803 | @@ -163,82 +165,6 @@ |
1804 | bus->send(reply); |
1805 | } |
1806 | |
1807 | - bool does_client_have_access(const std::string& context, const std::string& uri) |
1808 | - { |
1809 | - if (context.empty() || uri.empty()) |
1810 | - { |
1811 | - std::cout << "Client denied access since context or uri are empty" << std::endl; |
1812 | - return false; |
1813 | - } |
1814 | - |
1815 | - if (context == "unconfined") |
1816 | - { |
1817 | - std::cout << "Client allowed access since it's unconfined" << std::endl; |
1818 | - return true; |
1819 | - } |
1820 | - |
1821 | - size_t pos = context.find_first_of('_'); |
1822 | - if (pos == std::string::npos) |
1823 | - { |
1824 | - std::cout << "Client denied access since it's an invalid apparmor security context" << std::endl; |
1825 | - return false; |
1826 | - } |
1827 | - |
1828 | - const std::string pkgname = context.substr(0, pos); |
1829 | - std::cout << "client pkgname: " << pkgname << std::endl; |
1830 | - std::cout << "uri: " << uri << std::endl; |
1831 | - |
1832 | - // All confined apps can access their own files |
1833 | - if (uri.find(std::string(".local/share/" + pkgname + "/")) != std::string::npos |
1834 | - || uri.find(std::string(".cache/" + pkgname + "/")) != std::string::npos) |
1835 | - { |
1836 | - std::cout << "Client can access content in ~/.local/share/" << pkgname << " or ~/.cache/" << pkgname << std::endl; |
1837 | - return true; |
1838 | - } |
1839 | - else if (uri.find(std::string("opt/click.ubuntu.com/")) != std::string::npos |
1840 | - && uri.find(pkgname) != std::string::npos) |
1841 | - { |
1842 | - std::cout << "Client can access content in own opt directory" << std::endl; |
1843 | - return true; |
1844 | - } |
1845 | - else if ((uri.find(std::string("/system/media/audio/ui/")) != std::string::npos |
1846 | - || uri.find(std::string("/android/system/media/audio/ui/")) != std::string::npos) |
1847 | - && pkgname == "com.ubuntu.camera") |
1848 | - { |
1849 | - std::cout << "Camera app can access ui sounds" << std::endl; |
1850 | - return true; |
1851 | - } |
1852 | - // TODO: Check if the trust store previously allowed direct access to uri |
1853 | - |
1854 | - // Check in ~/Music and ~/Videos |
1855 | - // TODO: when the trust store lands, check it to see if this app can access the dirs and |
1856 | - // then remove the explicit whitelist of the music-app, and gallery-app |
1857 | - else if ((pkgname == "com.ubuntu.music" || pkgname == "com.ubuntu.gallery") && |
1858 | - (uri.find(std::string("Music/")) != std::string::npos |
1859 | - || uri.find(std::string("Videos/")) != std::string::npos |
1860 | - || uri.find(std::string("/media")) != std::string::npos)) |
1861 | - { |
1862 | - std::cout << "Client can access content in ~/Music or ~/Videos" << std::endl; |
1863 | - return true; |
1864 | - } |
1865 | - else if (uri.find(std::string("/usr/share/sounds")) != std::string::npos) |
1866 | - { |
1867 | - std::cout << "Client can access content in /usr/share/sounds" << std::endl; |
1868 | - return true; |
1869 | - } |
1870 | - else if (uri.find(std::string("http://")) != std::string::npos |
1871 | - || uri.find(std::string("rtsp://")) != std::string::npos) |
1872 | - { |
1873 | - std::cout << "Client can access streaming content" << std::endl; |
1874 | - return true; |
1875 | - } |
1876 | - else |
1877 | - { |
1878 | - std::cout << "Client denied access to open_uri()" << std::endl; |
1879 | - return false; |
1880 | - } |
1881 | - } |
1882 | - |
1883 | void handle_key(const core::dbus::Message::Ptr& in) |
1884 | { |
1885 | auto reply = dbus::Message::make_method_return(in); |
1886 | @@ -248,15 +174,15 @@ |
1887 | |
1888 | void handle_open_uri(const core::dbus::Message::Ptr& in) |
1889 | { |
1890 | - dbus_stub.get_connection_app_armor_security_async(in->sender(), [this, in](const std::string& profile) |
1891 | + request_context_resolver->resolve_context_for_dbus_name_async(in->sender(), [this, in](const media::apparmor::ubuntu::Context& context) |
1892 | { |
1893 | Track::UriType uri; |
1894 | in->reader() >> uri; |
1895 | |
1896 | - bool have_access = does_client_have_access(profile, uri); |
1897 | + auto result = request_authenticator->authenticate_open_uri_request(context, uri); |
1898 | |
1899 | auto reply = dbus::Message::make_method_return(in); |
1900 | - reply->writer() << (have_access ? impl->open_uri(uri) : false); |
1901 | + reply->writer() << (std::get<0>(result) ? impl->open_uri(uri) : false); |
1902 | |
1903 | bus->send(reply); |
1904 | }); |
1905 | @@ -264,16 +190,16 @@ |
1906 | |
1907 | void handle_open_uri_extended(const core::dbus::Message::Ptr& in) |
1908 | { |
1909 | - dbus_stub.get_connection_app_armor_security_async(in->sender(), [this, in](const std::string& profile) |
1910 | + request_context_resolver->resolve_context_for_dbus_name_async(in->sender(), [this, in](const media::apparmor::ubuntu::Context& context) |
1911 | { |
1912 | Track::UriType uri; |
1913 | Player::HeadersType headers; |
1914 | |
1915 | in->reader() >> uri >> headers; |
1916 | |
1917 | - bool have_access = does_client_have_access(profile, uri); |
1918 | + auto result = request_authenticator->authenticate_open_uri_request(context, uri); |
1919 | auto reply = dbus::Message::make_method_return(in); |
1920 | - reply->writer() << (have_access ? impl->open_uri(uri, headers) : false); |
1921 | + reply->writer() << (std::get<0>(result) ? impl->open_uri(uri, headers) : false); |
1922 | |
1923 | bus->send(reply); |
1924 | }); |
1925 | @@ -301,12 +227,10 @@ |
1926 | } |
1927 | |
1928 | media::PlayerSkeleton* impl; |
1929 | - std::string identity; |
1930 | dbus::Bus::Ptr bus; |
1931 | dbus::Object::Ptr object; |
1932 | - dbus::Object::Ptr apparmor_session; |
1933 | - |
1934 | - org::freedesktop::dbus::DBus::Stub dbus_stub; |
1935 | + media::apparmor::ubuntu::RequestContextResolver::Ptr request_context_resolver; |
1936 | + media::apparmor::ubuntu::RequestAuthenticator::Ptr request_authenticator; |
1937 | |
1938 | mpris::Player::Skeleton skeleton; |
1939 | |
1940 | @@ -314,11 +238,13 @@ |
1941 | { |
1942 | typedef core::dbus::Signal<mpris::Player::Signals::Seeked, mpris::Player::Signals::Seeked::ArgumentType> DBusSeekedToSignal; |
1943 | typedef core::dbus::Signal<mpris::Player::Signals::EndOfStream, mpris::Player::Signals::EndOfStream::ArgumentType> DBusEndOfStreamSignal; |
1944 | + typedef core::dbus::Signal<mpris::Player::Signals::AboutToFinish, mpris::Player::Signals::AboutToFinish::ArgumentType> DBusAboutToFinishSignal; |
1945 | typedef core::dbus::Signal<mpris::Player::Signals::PlaybackStatusChanged, mpris::Player::Signals::PlaybackStatusChanged::ArgumentType> DBusPlaybackStatusChangedSignal; |
1946 | typedef core::dbus::Signal<mpris::Player::Signals::VideoDimensionChanged, mpris::Player::Signals::VideoDimensionChanged::ArgumentType> DBusVideoDimensionChangedSignal; |
1947 | typedef core::dbus::Signal<mpris::Player::Signals::Error, mpris::Player::Signals::Error::ArgumentType> DBusErrorSignal; |
1948 | |
1949 | Signals(const std::shared_ptr<DBusSeekedToSignal>& remote_seeked, |
1950 | + const std::shared_ptr<DBusAboutToFinishSignal>& remote_atf, |
1951 | const std::shared_ptr<DBusEndOfStreamSignal>& remote_eos, |
1952 | const std::shared_ptr<DBusPlaybackStatusChangedSignal>& remote_playback_status_changed, |
1953 | const std::shared_ptr<DBusVideoDimensionChangedSignal>& remote_video_dimension_changed, |
1954 | @@ -329,6 +255,11 @@ |
1955 | remote_seeked->emit(value); |
1956 | }); |
1957 | |
1958 | + about_to_finish.connect([remote_atf]() |
1959 | + { |
1960 | + remote_atf->emit(); |
1961 | + }); |
1962 | + |
1963 | end_of_stream.connect([remote_eos]() |
1964 | { |
1965 | remote_eos->emit(); |
1966 | @@ -351,6 +282,7 @@ |
1967 | } |
1968 | |
1969 | core::Signal<int64_t> seeked_to; |
1970 | + core::Signal<void> about_to_finish; |
1971 | core::Signal<void> end_of_stream; |
1972 | core::Signal<media::Player::PlaybackStatus> playback_status_changed; |
1973 | core::Signal<media::video::Dimensions> video_dimension_changed; |
1974 | @@ -360,7 +292,7 @@ |
1975 | }; |
1976 | |
1977 | media::PlayerSkeleton::PlayerSkeleton(const media::PlayerSkeleton::Configuration& config) |
1978 | - : d(new Private{this, config.identity, config.bus, config.session}) |
1979 | + : d(new Private{this, config.bus, config.session, config.request_context_resolver, config.request_authenticator}) |
1980 | { |
1981 | // Setup method handlers for mpris::Player methods. |
1982 | auto next = std::bind(&Private::handle_next, d, std::placeholders::_1); |
1983 | @@ -635,6 +567,16 @@ |
1984 | return d->signals.seeked_to; |
1985 | } |
1986 | |
1987 | +const core::Signal<void>& media::PlayerSkeleton::about_to_finish() const |
1988 | +{ |
1989 | + return d->signals.about_to_finish; |
1990 | +} |
1991 | + |
1992 | +core::Signal<void>& media::PlayerSkeleton::about_to_finish() |
1993 | +{ |
1994 | + return d->signals.about_to_finish; |
1995 | +} |
1996 | + |
1997 | const core::Signal<void>& media::PlayerSkeleton::end_of_stream() const |
1998 | { |
1999 | return d->signals.end_of_stream; |
2000 | |
2001 | === modified file 'src/core/media/player_skeleton.h' |
2002 | --- src/core/media/player_skeleton.h 2015-03-16 15:38:27 +0000 |
2003 | +++ src/core/media/player_skeleton.h 2015-03-16 15:38:27 +0000 |
2004 | @@ -24,6 +24,7 @@ |
2005 | |
2006 | #include "player_traits.h" |
2007 | |
2008 | +#include "apparmor/ubuntu.h" |
2009 | #include "mpris/player.h" |
2010 | |
2011 | #include <core/dbus/skeleton.h> |
2012 | @@ -37,11 +38,29 @@ |
2013 | { |
2014 | namespace media |
2015 | { |
2016 | +namespace helper |
2017 | +{ |
2018 | +struct ExternalServices; |
2019 | +} |
2020 | + |
2021 | class Service; |
2022 | |
2023 | class PlayerSkeleton : public core::ubuntu::media::Player |
2024 | { |
2025 | public: |
2026 | + // All creation time arguments go here. |
2027 | + struct Configuration |
2028 | + { |
2029 | + // The bus connection we are associated with. |
2030 | + std::shared_ptr<core::dbus::Bus> bus; |
2031 | + // The session object that we want to expose the skeleton upon. |
2032 | + std::shared_ptr<core::dbus::Object> session; |
2033 | + // Our functional dependencies. |
2034 | + apparmor::ubuntu::RequestContextResolver::Ptr request_context_resolver; |
2035 | + apparmor::ubuntu::RequestAuthenticator::Ptr request_authenticator; |
2036 | + }; |
2037 | + |
2038 | + PlayerSkeleton(const Configuration& configuration); |
2039 | ~PlayerSkeleton(); |
2040 | |
2041 | virtual const core::Property<bool>& can_play() const; |
2042 | @@ -73,26 +92,11 @@ |
2043 | virtual core::Property<Lifetime>& lifetime(); |
2044 | |
2045 | virtual const core::Signal<int64_t>& seeked_to() const; |
2046 | + virtual const core::Signal<void>& about_to_finish() const; |
2047 | virtual const core::Signal<void>& end_of_stream() const; |
2048 | - virtual core::Signal<PlaybackStatus>& playback_status_changed(); |
2049 | virtual const core::Signal<video::Dimensions>& video_dimension_changed() const; |
2050 | virtual const core::Signal<Error>& error() const; |
2051 | |
2052 | -protected: |
2053 | - // All creation time arguments go here. |
2054 | - struct Configuration |
2055 | - { |
2056 | - // The bus connection we are associated with. |
2057 | - std::shared_ptr<core::dbus::Bus> bus; |
2058 | - // The session object that we want to expose the skeleton upon. |
2059 | - std::shared_ptr<core::dbus::Object> session; |
2060 | - // Our identity, an identifier we pass out to other parts of the system. |
2061 | - // Defaults to the short app id (${PKG_NAME}_${APP}). |
2062 | - std::string identity; |
2063 | - }; |
2064 | - |
2065 | - PlayerSkeleton(const Configuration& configuration); |
2066 | - |
2067 | // These properties are not exposed to the client, but still need to be |
2068 | // able to be settable from within the Player: |
2069 | virtual core::Property<PlaybackStatus>& playback_status(); |
2070 | @@ -111,7 +115,9 @@ |
2071 | virtual core::Property<Orientation>& orientation(); |
2072 | |
2073 | virtual core::Signal<int64_t>& seeked_to(); |
2074 | + virtual core::Signal<void>& about_to_finish(); |
2075 | virtual core::Signal<void>& end_of_stream(); |
2076 | + virtual core::Signal<PlaybackStatus>& playback_status_changed(); |
2077 | virtual core::Signal<video::Dimensions>& video_dimension_changed(); |
2078 | virtual core::Signal<Error>& error(); |
2079 | |
2080 | |
2081 | === modified file 'src/core/media/player_stub.cpp' |
2082 | --- src/core/media/player_stub.cpp 2015-03-16 15:38:27 +0000 |
2083 | +++ src/core/media/player_stub.cpp 2015-03-16 15:38:27 +0000 |
2084 | @@ -76,6 +76,7 @@ |
2085 | signals |
2086 | { |
2087 | object->get_signal<mpris::Player::Signals::Seeked>(), |
2088 | + object->get_signal<mpris::Player::Signals::AboutToFinish>(), |
2089 | object->get_signal<mpris::Player::Signals::EndOfStream>(), |
2090 | object->get_signal<mpris::Player::Signals::PlaybackStatusChanged>(), |
2091 | object->get_signal<mpris::Player::Signals::VideoDimensionChanged>(), |
2092 | @@ -122,17 +123,20 @@ |
2093 | struct Signals |
2094 | { |
2095 | typedef core::dbus::Signal<mpris::Player::Signals::Seeked, mpris::Player::Signals::Seeked::ArgumentType> DBusSeekedToSignal; |
2096 | + typedef core::dbus::Signal<mpris::Player::Signals::AboutToFinish, mpris::Player::Signals::AboutToFinish::ArgumentType> DBusAboutToFinishSignal; |
2097 | typedef core::dbus::Signal<mpris::Player::Signals::EndOfStream, mpris::Player::Signals::EndOfStream::ArgumentType> DBusEndOfStreamSignal; |
2098 | typedef core::dbus::Signal<mpris::Player::Signals::PlaybackStatusChanged, mpris::Player::Signals::PlaybackStatusChanged::ArgumentType> DBusPlaybackStatusChangedSignal; |
2099 | typedef core::dbus::Signal<mpris::Player::Signals::VideoDimensionChanged, mpris::Player::Signals::VideoDimensionChanged::ArgumentType> DBusVideoDimensionChangedSignal; |
2100 | typedef core::dbus::Signal<mpris::Player::Signals::Error, mpris::Player::Signals::Error::ArgumentType> DBusErrorSignal; |
2101 | |
2102 | Signals(const std::shared_ptr<DBusSeekedToSignal>& seeked, |
2103 | + const std::shared_ptr<DBusAboutToFinishSignal>& atf, |
2104 | const std::shared_ptr<DBusEndOfStreamSignal>& eos, |
2105 | const std::shared_ptr<DBusPlaybackStatusChangedSignal>& status, |
2106 | const std::shared_ptr<DBusVideoDimensionChangedSignal>& d, |
2107 | const std::shared_ptr<DBusErrorSignal>& e) |
2108 | : seeked_to(), |
2109 | + about_to_finish(), |
2110 | end_of_stream(), |
2111 | playback_status_changed(), |
2112 | video_dimension_changed(), |
2113 | @@ -140,6 +144,7 @@ |
2114 | dbus |
2115 | { |
2116 | seeked, |
2117 | + atf, |
2118 | eos, |
2119 | status, |
2120 | d, |
2121 | @@ -152,6 +157,12 @@ |
2122 | seeked_to(value); |
2123 | }); |
2124 | |
2125 | + dbus.about_to_finish->connect([this]() |
2126 | + { |
2127 | + std::cout << "AboutToFinish signal arrived via the bus." << std::endl; |
2128 | + about_to_finish(); |
2129 | + }); |
2130 | + |
2131 | dbus.end_of_stream->connect([this]() |
2132 | { |
2133 | std::cout << "EndOfStream signal arrived via the bus." << std::endl; |
2134 | @@ -178,6 +189,7 @@ |
2135 | } |
2136 | |
2137 | core::Signal<int64_t> seeked_to; |
2138 | + core::Signal<void> about_to_finish; |
2139 | core::Signal<void> end_of_stream; |
2140 | core::Signal<media::Player::PlaybackStatus> playback_status_changed; |
2141 | core::Signal<media::video::Dimensions> video_dimension_changed; |
2142 | @@ -186,6 +198,7 @@ |
2143 | struct DBus |
2144 | { |
2145 | std::shared_ptr<DBusSeekedToSignal> seeked_to; |
2146 | + std::shared_ptr<DBusAboutToFinishSignal> about_to_finish; |
2147 | std::shared_ptr<DBusEndOfStreamSignal> end_of_stream; |
2148 | std::shared_ptr<DBusPlaybackStatusChangedSignal> playback_status_changed; |
2149 | std::shared_ptr<DBusVideoDimensionChangedSignal> video_dimension_changed; |
2150 | @@ -434,6 +447,11 @@ |
2151 | return d->signals.seeked_to; |
2152 | } |
2153 | |
2154 | +const core::Signal<void>& media::PlayerStub::about_to_finish() const |
2155 | +{ |
2156 | + return d->signals.about_to_finish; |
2157 | +} |
2158 | + |
2159 | const core::Signal<void>& media::PlayerStub::end_of_stream() const |
2160 | { |
2161 | return d->signals.end_of_stream; |
2162 | |
2163 | === modified file 'src/core/media/player_stub.h' |
2164 | --- src/core/media/player_stub.h 2015-03-16 15:38:27 +0000 |
2165 | +++ src/core/media/player_stub.h 2015-03-16 15:38:27 +0000 |
2166 | @@ -86,6 +86,7 @@ |
2167 | virtual core::Property<Lifetime>& lifetime(); |
2168 | |
2169 | virtual const core::Signal<int64_t>& seeked_to() const; |
2170 | + virtual const core::Signal<void>& about_to_finish() const; |
2171 | virtual const core::Signal<void>& end_of_stream() const; |
2172 | virtual core::Signal<PlaybackStatus>& playback_status_changed(); |
2173 | virtual const core::Signal<video::Dimensions>& video_dimension_changed() const; |
2174 | |
2175 | === modified file 'src/core/media/power/state_controller.cpp' |
2176 | --- src/core/media/power/state_controller.cpp 2015-03-16 15:38:27 +0000 |
2177 | +++ src/core/media/power/state_controller.cpp 2015-03-16 15:38:27 +0000 |
2178 | @@ -128,42 +128,38 @@ |
2179 | if (cookie == the_invalid_cookie) |
2180 | return; |
2181 | |
2182 | - std::weak_ptr<DisplayStateLock> wp{shared_from_this()}; |
2183 | + // We make sure that we keep ourselves alive to make sure |
2184 | + // that release requests are always correctly issued. |
2185 | + auto sp = shared_from_this(); |
2186 | |
2187 | auto current_cookie(cookie); |
2188 | |
2189 | timeout.expires_from_now(timeout_for_release()); |
2190 | - timeout.async_wait([wp, state, current_cookie](const boost::system::error_code& ec) |
2191 | + timeout.async_wait([sp, state, current_cookie](const boost::system::error_code& ec) |
2192 | { |
2193 | // We only return early from the timeout handler if the operation has been |
2194 | // explicitly aborted before. |
2195 | if (ec == boost::asio::error::operation_aborted) |
2196 | return; |
2197 | |
2198 | - if (auto sp = wp.lock()) |
2199 | - { |
2200 | - sp->object->invoke_method_asynchronously_with_callback<com::canonical::Unity::Screen::removeDisplayOnRequest, void>( |
2201 | - [wp, state, current_cookie](const core::dbus::Result<void>& result) |
2202 | + sp->object->invoke_method_asynchronously_with_callback<com::canonical::Unity::Screen::removeDisplayOnRequest, void>( |
2203 | + [sp, state, current_cookie](const core::dbus::Result<void>& result) |
2204 | + { |
2205 | + if (result.is_error()) |
2206 | { |
2207 | - if (result.is_error()) |
2208 | - { |
2209 | - std::cerr << result.error().print() << std::endl; |
2210 | - return; |
2211 | - } |
2212 | - |
2213 | - if (auto sp = wp.lock()) |
2214 | - { |
2215 | - sp->signals.released(state); |
2216 | - |
2217 | - // We might have issued a different request before and |
2218 | - // only call the display state done if the original cookie |
2219 | - // corresponds to the one we just gave up. |
2220 | - if (sp->cookie == current_cookie) |
2221 | - sp->cookie = the_invalid_cookie; |
2222 | - } |
2223 | - |
2224 | - }, current_cookie); |
2225 | - } |
2226 | + std::cerr << result.error().print() << std::endl; |
2227 | + return; |
2228 | + } |
2229 | + |
2230 | + sp->signals.released(state); |
2231 | + |
2232 | + // We might have issued a different request before and |
2233 | + // only call the display state done if the original cookie |
2234 | + // corresponds to the one we just gave up. |
2235 | + if (sp->cookie == current_cookie) |
2236 | + sp->cookie = the_invalid_cookie; |
2237 | + |
2238 | + }, current_cookie); |
2239 | }); |
2240 | } |
2241 | |
2242 | |
2243 | === modified file 'src/core/media/server/server.cpp' |
2244 | --- src/core/media/server/server.cpp 2015-03-16 15:38:27 +0000 |
2245 | +++ src/core/media/server/server.cpp 2015-03-16 15:38:27 +0000 |
2246 | @@ -20,6 +20,7 @@ |
2247 | #include <core/media/player.h> |
2248 | #include <core/media/track_list.h> |
2249 | |
2250 | +#include "core/media/hashed_keyed_player_store.h" |
2251 | #include "core/media/service_implementation.h" |
2252 | |
2253 | #include <core/posix/signal.h> |
2254 | @@ -103,23 +104,37 @@ |
2255 | } |
2256 | }; |
2257 | |
2258 | + // Our common player store instance for tracking player instances. |
2259 | + auto player_store = std::make_shared<media::HashedKeyedPlayerStore>(); |
2260 | // We assemble the configuration for executing the service now. |
2261 | media::ServiceImplementation::Configuration service_config |
2262 | { |
2263 | + std::make_shared<media::HashedKeyedPlayerStore>(), |
2264 | external_services |
2265 | }; |
2266 | |
2267 | - auto service = std::make_shared<media::ServiceImplementation>(service_config); |
2268 | + auto impl = std::make_shared<media::ServiceImplementation>(media::ServiceImplementation::Configuration |
2269 | + { |
2270 | + player_store, |
2271 | + external_services |
2272 | + }); |
2273 | + |
2274 | + auto skeleton = std::make_shared<media::ServiceSkeleton>(media::ServiceSkeleton::Configuration |
2275 | + { |
2276 | + impl, |
2277 | + player_store, |
2278 | + |
2279 | + }); |
2280 | |
2281 | std::thread service_worker |
2282 | { |
2283 | - [&shutdown_requested, service]() |
2284 | + [&shutdown_requested, skeleton]() |
2285 | { |
2286 | while (not shutdown_requested) |
2287 | { |
2288 | try |
2289 | { |
2290 | - service->run(); |
2291 | + skeleton->run(); |
2292 | } |
2293 | catch (const std::exception& e) |
2294 | { |
2295 | @@ -142,7 +157,7 @@ |
2296 | shutdown_requested = true; |
2297 | |
2298 | // And stop execution of helper and actual service. |
2299 | - service->stop(); |
2300 | + skeleton->stop(); |
2301 | |
2302 | if (service_worker.joinable()) |
2303 | service_worker.join(); |
2304 | |
2305 | === modified file 'src/core/media/service_implementation.cpp' |
2306 | --- src/core/media/service_implementation.cpp 2015-03-16 15:38:27 +0000 |
2307 | +++ src/core/media/service_implementation.cpp 2015-03-16 15:38:27 +0000 |
2308 | @@ -22,14 +22,16 @@ |
2309 | |
2310 | #include "service_implementation.h" |
2311 | |
2312 | +#include "apparmor/ubuntu.h" |
2313 | #include "audio/output_observer.h" |
2314 | #include "client_death_observer.h" |
2315 | -#include "call-monitor/call_monitor.h" |
2316 | #include "player_configuration.h" |
2317 | +#include "player_skeleton.h" |
2318 | #include "player_implementation.h" |
2319 | #include "power/battery_observer.h" |
2320 | #include "power/state_controller.h" |
2321 | #include "recorder_observer.h" |
2322 | +#include "telephony/call_monitor.h" |
2323 | |
2324 | #include <boost/asio.hpp> |
2325 | |
2326 | @@ -59,8 +61,10 @@ |
2327 | client_death_observer(media::platform_default_client_death_observer()), |
2328 | recorder_observer(media::make_platform_default_recorder_observer()), |
2329 | audio_output_observer(media::audio::make_platform_default_output_observer()), |
2330 | + request_context_resolver(media::apparmor::ubuntu::make_platform_default_request_context_resolver(configuration.external_services)), |
2331 | + request_authenticator(media::apparmor::ubuntu::make_platform_default_request_authenticator()), |
2332 | audio_output_state(media::audio::OutputState::Speaker), |
2333 | - call_monitor(new CallMonitor) |
2334 | + call_monitor(media::telephony::make_platform_default_call_monitor()) |
2335 | { |
2336 | } |
2337 | |
2338 | @@ -74,13 +78,16 @@ |
2339 | media::ClientDeathObserver::Ptr client_death_observer; |
2340 | media::RecorderObserver::Ptr recorder_observer; |
2341 | media::audio::OutputObserver::Ptr audio_output_observer; |
2342 | - media::audio::OutputObserver audio_output_state; |
2343 | + media::apparmor::ubuntu::RequestContextResolver::Ptr request_context_resolver; |
2344 | + media::apparmor::ubuntu::RequestAuthenticator::Ptr request_authenticator; |
2345 | + media::audio::OutputState audio_output_state; |
2346 | |
2347 | - std::unique_ptr<CallMonitor> call_monitor; |
2348 | + media::telephony::CallMonitor::Ptr call_monitor; |
2349 | std::list<media::Player::PlayerKey> paused_sessions; |
2350 | }; |
2351 | |
2352 | -media::ServiceImplementation::ServiceImplementation(const Configuration& configuration) : d(new Private(configuration)) |
2353 | +media::ServiceImplementation::ServiceImplementation(const Configuration& configuration) |
2354 | + : d(new Private(configuration)) |
2355 | { |
2356 | d->battery_observer->level().changed().connect([this](const media::power::Level& level) |
2357 | { |
2358 | @@ -118,22 +125,20 @@ |
2359 | break; |
2360 | case audio::OutputState::External: |
2361 | std::cout << "AudioOutputObserver reports that output is now External." << std::endl; |
2362 | - if (d->audio_output_state == audio::OutputState::Earpiece) |
2363 | - pause_all_multimedia_sessions(); |
2364 | break; |
2365 | } |
2366 | d->audio_output_state = state; |
2367 | }); |
2368 | |
2369 | - d->call_monitor->on_change([this](CallMonitor::State state) { |
2370 | + d->call_monitor->on_call_state_changed().connect([this](media::telephony::CallMonitor::State state) |
2371 | + { |
2372 | switch (state) { |
2373 | - case CallMonitor::OffHook: |
2374 | + case media::telephony::CallMonitor::State::OffHook: |
2375 | std::cout << "Got call started signal, pausing all multimedia sessions" << std::endl; |
2376 | pause_all_multimedia_sessions(); |
2377 | break; |
2378 | - case CallMonitor::OnHook: |
2379 | + case media::telephony::CallMonitor::State::OnHook: |
2380 | std::cout << "Got call ended signal, resuming paused multimedia sessions" << std::endl; |
2381 | - // Don't auto-resume any paused video playback sessions |
2382 | resume_paused_multimedia_sessions(false); |
2383 | break; |
2384 | } |
2385 | @@ -160,12 +165,15 @@ |
2386 | std::shared_ptr<media::Player> media::ServiceImplementation::create_session( |
2387 | const media::Player::Configuration& conf) |
2388 | { |
2389 | - auto player = std::make_shared<media::PlayerImplementation>(media::PlayerImplementation::Configuration |
2390 | + auto player = std::make_shared<media::PlayerImplementation<media::PlayerSkeleton>>(media::PlayerImplementation<media::PlayerSkeleton>::Configuration |
2391 | { |
2392 | - conf.identity, |
2393 | - conf.bus, |
2394 | - conf.session, |
2395 | - shared_from_this(), |
2396 | + media::PlayerSkeleton::Configuration |
2397 | + { |
2398 | + conf.bus, |
2399 | + conf.session, |
2400 | + d->request_context_resolver, |
2401 | + d->request_authenticator |
2402 | + }, |
2403 | conf.key, |
2404 | d->client_death_observer, |
2405 | d->power_state_controller |
2406 | @@ -181,11 +189,11 @@ |
2407 | // until all dispatches are done |
2408 | d->configuration.external_services.io_service.post([this, key]() |
2409 | { |
2410 | - if (!has_player_for_key(key)) |
2411 | + if (!d->configuration.player_store->has_player_for_key(key)) |
2412 | return; |
2413 | |
2414 | - if (player_for_key(key)->lifetime() == Player::Lifetime::normal) |
2415 | - remove_player_for_key(key); |
2416 | + if (d->configuration.player_store->player_for_key(key)->lifetime() == Player::Lifetime::normal) |
2417 | + d->configuration.player_store->remove_player_for_key(key); |
2418 | }); |
2419 | }); |
2420 | |
2421 | @@ -206,19 +214,19 @@ |
2422 | |
2423 | void media::ServiceImplementation::pause_other_sessions(media::Player::PlayerKey key) |
2424 | { |
2425 | - if (not has_player_for_key(key)) |
2426 | + if (not d->configuration.player_store->has_player_for_key(key)) |
2427 | { |
2428 | cerr << "Could not find Player by key: " << key << endl; |
2429 | return; |
2430 | } |
2431 | |
2432 | - auto current_player = player_for_key(key); |
2433 | + auto current_player = d->configuration.player_store->player_for_key(key); |
2434 | |
2435 | // We immediately make the player known as new current player. |
2436 | if (current_player->audio_stream_role() == media::Player::multimedia) |
2437 | - set_current_player_for_key(key); |
2438 | + d->configuration.player_store->set_current_player_for_key(key); |
2439 | |
2440 | - enumerate_players([current_player, key](const media::Player::PlayerKey& other_key, const std::shared_ptr<media::Player>& other_player) |
2441 | + d->configuration.player_store->enumerate_players([current_player, key](const media::Player::PlayerKey& other_key, const std::shared_ptr<media::Player>& other_player) |
2442 | { |
2443 | // Only pause a Player if all of the following criteria are met: |
2444 | // 1) currently playing |
2445 | @@ -238,7 +246,7 @@ |
2446 | |
2447 | void media::ServiceImplementation::pause_all_multimedia_sessions() |
2448 | { |
2449 | - enumerate_players([this](const media::Player::PlayerKey& key, const std::shared_ptr<media::Player>& player) |
2450 | + d->configuration.player_store->enumerate_players([this](const media::Player::PlayerKey& key, const std::shared_ptr<media::Player>& player) |
2451 | { |
2452 | if (player->playback_status() == Player::playing |
2453 | && player->audio_stream_role() == media::Player::multimedia) |
2454 | @@ -253,7 +261,7 @@ |
2455 | void media::ServiceImplementation::resume_paused_multimedia_sessions(bool resume_video_sessions) |
2456 | { |
2457 | std::for_each(d->paused_sessions.begin(), d->paused_sessions.end(), [this, resume_video_sessions](const media::Player::PlayerKey& key) { |
2458 | - auto player = player_for_key(key); |
2459 | + auto player = d->configuration.player_store->player_for_key(key); |
2460 | // Only resume video playback if explicitly desired |
2461 | if (resume_video_sessions || player->is_audio_source()) |
2462 | player->play(); |
2463 | @@ -266,10 +274,10 @@ |
2464 | |
2465 | void media::ServiceImplementation::resume_multimedia_session() |
2466 | { |
2467 | - if (not has_player_for_key(d->resume_key)) |
2468 | + if (not d->configuration.player_store->has_player_for_key(d->resume_key)) |
2469 | return; |
2470 | |
2471 | - auto player = player_for_key(d->resume_key); |
2472 | + auto player = d->configuration.player_store->player_for_key(d->resume_key); |
2473 | |
2474 | if (player->playback_status() == Player::paused) |
2475 | { |
2476 | |
2477 | === modified file 'src/core/media/service_implementation.h' |
2478 | --- src/core/media/service_implementation.h 2015-03-16 15:38:27 +0000 |
2479 | +++ src/core/media/service_implementation.h 2015-03-16 15:38:27 +0000 |
2480 | @@ -30,12 +30,13 @@ |
2481 | { |
2482 | class Player; |
2483 | |
2484 | -class ServiceImplementation : public ServiceSkeleton |
2485 | +class ServiceImplementation : public Service |
2486 | { |
2487 | public: |
2488 | // All creation time arguments go here. |
2489 | struct Configuration |
2490 | { |
2491 | + KeyedPlayerStore::Ptr player_store; |
2492 | helper::ExternalServices& external_services; |
2493 | }; |
2494 | |
2495 | |
2496 | === modified file 'src/core/media/service_skeleton.cpp' |
2497 | --- src/core/media/service_skeleton.cpp 2014-11-18 20:29:26 +0000 |
2498 | +++ src/core/media/service_skeleton.cpp 2015-03-16 15:38:27 +0000 |
2499 | @@ -19,8 +19,6 @@ |
2500 | |
2501 | #include "service_skeleton.h" |
2502 | |
2503 | -#include "apparmor.h" |
2504 | - |
2505 | #include "mpris/media_player2.h" |
2506 | #include "mpris/metadata.h" |
2507 | #include "mpris/player.h" |
2508 | @@ -51,12 +49,12 @@ |
2509 | |
2510 | struct media::ServiceSkeleton::Private |
2511 | { |
2512 | - Private(media::ServiceSkeleton* impl, const media::CoverArtResolver& resolver) |
2513 | + Private(media::ServiceSkeleton* impl, const ServiceSkeleton::Configuration& config) |
2514 | : impl(impl), |
2515 | object(impl->access_service()->add_object_for_path( |
2516 | - dbus::traits::Service<media::Service>::object_path())), |
2517 | - dbus_stub(impl->access_bus()), |
2518 | - exported(impl->access_bus(), resolver) |
2519 | + dbus::traits::Service<media::Service>::object_path())), |
2520 | + exported(impl->access_bus(), config.cover_art_resolver), |
2521 | + configuration(config) |
2522 | { |
2523 | object->install_method_handler<mpris::Service::CreateSession>( |
2524 | std::bind( |
2525 | @@ -99,129 +97,71 @@ |
2526 | dbus::types::ObjectPath op{session_info.first}; |
2527 | media::Player::PlayerKey key{session_info.second}; |
2528 | |
2529 | - dbus_stub.get_connection_app_armor_security_async(msg->sender(), [this, msg, op, key](const std::string& profile) |
2530 | - { |
2531 | - media::Player::Configuration config |
2532 | - { |
2533 | - profile, |
2534 | - key, |
2535 | - impl->access_bus(), |
2536 | - impl->access_service()->add_object_for_path(op) |
2537 | - }; |
2538 | - |
2539 | - try |
2540 | - { |
2541 | + media::Player::Configuration config |
2542 | + { |
2543 | + key, |
2544 | + impl->access_bus(), |
2545 | + impl->access_service()->add_object_for_path(op) |
2546 | + }; |
2547 | + |
2548 | + try |
2549 | + { |
2550 | + configuration.player_store->add_player_for_key(key, impl->create_session(config)); |
2551 | + auto reply = dbus::Message::make_method_return(msg); |
2552 | + reply->writer() << op; |
2553 | + |
2554 | + impl->access_bus()->send(reply); |
2555 | + } catch(const std::runtime_error& e) |
2556 | + { |
2557 | + auto reply = dbus::Message::make_error( |
2558 | + msg, |
2559 | + mpris::Service::Errors::CreatingSession::name(), |
2560 | + e.what()); |
2561 | + impl->access_bus()->send(reply); |
2562 | + } |
2563 | + } |
2564 | + |
2565 | + void handle_create_fixed_session(const core::dbus::Message::Ptr& msg) |
2566 | + { |
2567 | + try |
2568 | + { |
2569 | + std::string name; |
2570 | + msg->reader() >> name; |
2571 | + |
2572 | + if (named_player_map.count(name) == 0) { |
2573 | + // Create new session |
2574 | + auto session_info = create_session_info(); |
2575 | + |
2576 | + dbus::types::ObjectPath op{session_info.first}; |
2577 | + media::Player::PlayerKey key{session_info.second}; |
2578 | + |
2579 | + media::Player::Configuration config |
2580 | + { |
2581 | + key, |
2582 | + impl->access_bus(), |
2583 | + impl->access_service()->add_object_for_path(op) |
2584 | + }; |
2585 | + |
2586 | auto session = impl->create_session(config); |
2587 | - |
2588 | - bool inserted = false; |
2589 | - std::tie(std::ignore, inserted) |
2590 | - = session_store.insert(std::make_pair(key, session)); |
2591 | - |
2592 | - if (!inserted) |
2593 | - throw std::runtime_error("Problem persisting session in session store."); |
2594 | - |
2595 | + session->lifetime().set(media::Player::Lifetime::resumable); |
2596 | + |
2597 | + configuration.player_store->add_player_for_key(key, session); |
2598 | + |
2599 | + |
2600 | + named_player_map.insert(std::make_pair(name, key)); |
2601 | |
2602 | auto reply = dbus::Message::make_method_return(msg); |
2603 | reply->writer() << op; |
2604 | |
2605 | impl->access_bus()->send(reply); |
2606 | - } catch(const std::runtime_error& e) |
2607 | - { |
2608 | - auto reply = dbus::Message::make_error( |
2609 | - msg, |
2610 | - mpris::Service::Errors::CreatingSession::name(), |
2611 | - e.what()); |
2612 | - impl->access_bus()->send(reply); |
2613 | - } |
2614 | - }); |
2615 | - } |
2616 | - |
2617 | - void handle_create_fixed_session(const core::dbus::Message::Ptr& msg) |
2618 | - { |
2619 | - dbus_stub.get_connection_app_armor_security_async(msg->sender(), [this, msg](const std::string& profile) |
2620 | - { |
2621 | - try |
2622 | - { |
2623 | - std::string name; |
2624 | - msg->reader() >> name; |
2625 | - |
2626 | - if (fixed_session_store.count(name) == 0) { |
2627 | - // Create new session |
2628 | - auto session_info = create_session_info(); |
2629 | - |
2630 | - dbus::types::ObjectPath op{session_info.first}; |
2631 | - media::Player::PlayerKey key{session_info.second}; |
2632 | - |
2633 | - media::Player::Configuration config |
2634 | - { |
2635 | - profile, |
2636 | - key, |
2637 | - impl->access_bus(), |
2638 | - impl->access_service()->add_object_for_path(op) |
2639 | - }; |
2640 | - |
2641 | - auto session = impl->create_session(config); |
2642 | - session->lifetime().set(media::Player::Lifetime::resumable); |
2643 | - |
2644 | - bool inserted = false; |
2645 | - std::tie(std::ignore, inserted) |
2646 | - = session_store.insert(std::make_pair(key, session)); |
2647 | - |
2648 | - if (!inserted) |
2649 | - throw std::runtime_error("Problem persisting session in session store."); |
2650 | - |
2651 | - fixed_session_store.insert(std::make_pair(name, key)); |
2652 | - |
2653 | - auto reply = dbus::Message::make_method_return(msg); |
2654 | - reply->writer() << op; |
2655 | - |
2656 | - impl->access_bus()->send(reply); |
2657 | - } |
2658 | - else { |
2659 | - // Resume previous session |
2660 | - auto key = fixed_session_store[name]; |
2661 | - if (session_store.count(key) == 0) { |
2662 | - auto reply = dbus::Message::make_error( |
2663 | - msg, |
2664 | - mpris::Service::Errors::CreatingFixedSession::name(), |
2665 | - "Unable to locate player session"); |
2666 | - impl->access_bus()->send(reply); |
2667 | - return; |
2668 | - } |
2669 | - |
2670 | - std::stringstream ss; |
2671 | - ss << "/core/ubuntu/media/Service/sessions/" << key; |
2672 | - dbus::types::ObjectPath op{ss.str()}; |
2673 | - |
2674 | - auto reply = dbus::Message::make_method_return(msg); |
2675 | - reply->writer() << op; |
2676 | - |
2677 | - impl->access_bus()->send(reply); |
2678 | - } |
2679 | - } catch(const std::runtime_error& e) |
2680 | - { |
2681 | - auto reply = dbus::Message::make_error( |
2682 | - msg, |
2683 | - mpris::Service::Errors::CreatingSession::name(), |
2684 | - e.what()); |
2685 | - impl->access_bus()->send(reply); |
2686 | - } |
2687 | - }); |
2688 | - } |
2689 | - |
2690 | - void handle_resume_session(const core::dbus::Message::Ptr& msg) |
2691 | - { |
2692 | - dbus_stub.get_connection_app_armor_security_async(msg->sender(), [this, msg](const std::string&) |
2693 | - { |
2694 | - try |
2695 | - { |
2696 | - Player::PlayerKey key; |
2697 | - msg->reader() >> key; |
2698 | - |
2699 | - if (session_store.count(key) == 0) { |
2700 | + } |
2701 | + else { |
2702 | + // Resume previous session |
2703 | + auto key = named_player_map.at(name); |
2704 | + if (not configuration.player_store->has_player_for_key(key)) { |
2705 | auto reply = dbus::Message::make_error( |
2706 | msg, |
2707 | - mpris::Service::Errors::ResumingSession::name(), |
2708 | + mpris::Service::Errors::CreatingFixedSession::name(), |
2709 | "Unable to locate player session"); |
2710 | impl->access_bus()->send(reply); |
2711 | return; |
2712 | @@ -235,15 +175,49 @@ |
2713 | reply->writer() << op; |
2714 | |
2715 | impl->access_bus()->send(reply); |
2716 | - } catch(const std::runtime_error& e) |
2717 | - { |
2718 | + } |
2719 | + } catch(const std::runtime_error& e) |
2720 | + { |
2721 | + auto reply = dbus::Message::make_error( |
2722 | + msg, |
2723 | + mpris::Service::Errors::CreatingSession::name(), |
2724 | + e.what()); |
2725 | + impl->access_bus()->send(reply); |
2726 | + } |
2727 | + } |
2728 | + |
2729 | + void handle_resume_session(const core::dbus::Message::Ptr& msg) |
2730 | + { |
2731 | + try |
2732 | + { |
2733 | + Player::PlayerKey key; |
2734 | + msg->reader() >> key; |
2735 | + |
2736 | + if (not configuration.player_store->has_player_for_key(key)) { |
2737 | auto reply = dbus::Message::make_error( |
2738 | msg, |
2739 | - mpris::Service::Errors::CreatingSession::name(), |
2740 | - e.what()); |
2741 | + mpris::Service::Errors::ResumingSession::name(), |
2742 | + "Unable to locate player session"); |
2743 | impl->access_bus()->send(reply); |
2744 | + return; |
2745 | } |
2746 | - }); |
2747 | + |
2748 | + std::stringstream ss; |
2749 | + ss << "/core/ubuntu/media/Service/sessions/" << key; |
2750 | + dbus::types::ObjectPath op{ss.str()}; |
2751 | + |
2752 | + auto reply = dbus::Message::make_method_return(msg); |
2753 | + reply->writer() << op; |
2754 | + |
2755 | + impl->access_bus()->send(reply); |
2756 | + } catch(const std::runtime_error& e) |
2757 | + { |
2758 | + auto reply = dbus::Message::make_error( |
2759 | + msg, |
2760 | + mpris::Service::Errors::CreatingSession::name(), |
2761 | + e.what()); |
2762 | + impl->access_bus()->send(reply); |
2763 | + } |
2764 | } |
2765 | |
2766 | void handle_pause_other_sessions(const core::dbus::Message::Ptr& msg) |
2767 | @@ -260,11 +234,10 @@ |
2768 | media::ServiceSkeleton* impl; |
2769 | dbus::Object::Ptr object; |
2770 | |
2771 | - // We query the apparmor profile to obtain an identity for players. |
2772 | - org::freedesktop::dbus::DBus::Stub dbus_stub; |
2773 | - // We track all running player instances. |
2774 | - std::map<media::Player::PlayerKey, std::shared_ptr<media::Player>> session_store; |
2775 | - std::map<std::string, media::Player::PlayerKey> fixed_session_store; |
2776 | + // We remember all our creation time arguments. |
2777 | + ServiceSkeleton::Configuration configuration; |
2778 | + // We map named/fixed player instances to their respective keys. |
2779 | + std::map<std::string, media::Player::PlayerKey> named_player_map; |
2780 | // We expose the entire service as an MPRIS player. |
2781 | struct Exported |
2782 | { |
2783 | @@ -498,7 +471,7 @@ |
2784 | mpris::Player::Skeleton player; |
2785 | mpris::Playlists::Skeleton playlists; |
2786 | |
2787 | - // Helper to resolve (title, artist, album) tuples to cover art. |
2788 | + // The CoverArtResolver used by the exported player. |
2789 | media::CoverArtResolver cover_art_resolver; |
2790 | // The actual player instance. |
2791 | std::weak_ptr<media::Player> current_player; |
2792 | @@ -533,9 +506,9 @@ |
2793 | } exported; |
2794 | }; |
2795 | |
2796 | -media::ServiceSkeleton::ServiceSkeleton(const media::CoverArtResolver& resolver) |
2797 | +media::ServiceSkeleton::ServiceSkeleton(const Configuration& configuration) |
2798 | : dbus::Skeleton<media::Service>(the_session_bus()), |
2799 | - d(new Private(this, resolver)) |
2800 | + d(new Private(this, configuration)) |
2801 | { |
2802 | } |
2803 | |
2804 | @@ -543,46 +516,24 @@ |
2805 | { |
2806 | } |
2807 | |
2808 | -bool media::ServiceSkeleton::has_player_for_key(const media::Player::PlayerKey& key) const |
2809 | -{ |
2810 | - return d->session_store.count(key) > 0; |
2811 | -} |
2812 | - |
2813 | -std::shared_ptr<media::Player> media::ServiceSkeleton::player_for_key(const media::Player::PlayerKey& key) const |
2814 | -{ |
2815 | - return d->session_store.at(key); |
2816 | -} |
2817 | - |
2818 | -void media::ServiceSkeleton::enumerate_players(const media::ServiceSkeleton::PlayerEnumerator& enumerator) const |
2819 | -{ |
2820 | - for (const auto& pair : d->session_store) |
2821 | - enumerator(pair.first, pair.second); |
2822 | -} |
2823 | - |
2824 | -void media::ServiceSkeleton::set_current_player_for_key(const media::Player::PlayerKey& key) |
2825 | -{ |
2826 | - if (not has_player_for_key(key)) |
2827 | - return; |
2828 | - |
2829 | - d->exported.set_current_player(player_for_key(key)); |
2830 | -} |
2831 | - |
2832 | -void media::ServiceSkeleton::remove_player_for_key(const media::Player::PlayerKey& key) |
2833 | -{ |
2834 | - if (not has_player_for_key(key)) |
2835 | - return; |
2836 | - |
2837 | - auto player = player_for_key(key); |
2838 | - |
2839 | - d->session_store.erase(key); |
2840 | - d->exported.unset_if_current(player); |
2841 | - // All non-durable fixed sessions are also removed |
2842 | - for (auto it: d->fixed_session_store) { |
2843 | - if (it.second == key) { |
2844 | - d->fixed_session_store.erase(it.first); |
2845 | - break; |
2846 | - } |
2847 | - } |
2848 | +std::shared_ptr<media::Player> media::ServiceSkeleton::create_session(const media::Player::Configuration& config) |
2849 | +{ |
2850 | + return d->configuration.impl->create_session(config); |
2851 | +} |
2852 | + |
2853 | +std::shared_ptr<media::Player> media::ServiceSkeleton::create_fixed_session(const std::string& name, const media::Player::Configuration&config) |
2854 | +{ |
2855 | + return d->configuration.impl->create_fixed_session(name, config); |
2856 | +} |
2857 | + |
2858 | +std::shared_ptr<media::Player> media::ServiceSkeleton::resume_session(media::Player::PlayerKey key) |
2859 | +{ |
2860 | + return d->configuration.impl->resume_session(key); |
2861 | +} |
2862 | + |
2863 | +void media::ServiceSkeleton::pause_other_sessions(media::Player::PlayerKey key) |
2864 | +{ |
2865 | + d->configuration.impl->pause_other_sessions(key); |
2866 | } |
2867 | |
2868 | void media::ServiceSkeleton::run() |
2869 | |
2870 | === modified file 'src/core/media/service_skeleton.h' |
2871 | --- src/core/media/service_skeleton.h 2014-09-09 21:27:29 +0000 |
2872 | +++ src/core/media/service_skeleton.h 2015-03-16 15:38:27 +0000 |
2873 | @@ -22,6 +22,7 @@ |
2874 | #include <core/media/service.h> |
2875 | |
2876 | #include "cover_art_resolver.h" |
2877 | +#include "keyed_player_store.h" |
2878 | #include "service_traits.h" |
2879 | |
2880 | #include <core/dbus/skeleton.h> |
2881 | @@ -37,34 +38,22 @@ |
2882 | class ServiceSkeleton : public core::dbus::Skeleton<core::ubuntu::media::Service> |
2883 | { |
2884 | public: |
2885 | - // Functor for enumerating all known (key, player) pairs. |
2886 | - typedef std::function |
2887 | - < |
2888 | - void( |
2889 | - // The key of the player. |
2890 | - const core::ubuntu::media::Player::PlayerKey&, |
2891 | - // The actual player instance. |
2892 | - const std::shared_ptr<core::ubuntu::media::Player>& |
2893 | - ) |
2894 | - > PlayerEnumerator; |
2895 | + // Creation time arguments go here. |
2896 | + struct Configuration |
2897 | + { |
2898 | + std::shared_ptr<Service> impl; |
2899 | + KeyedPlayerStore::Ptr player_store; |
2900 | + CoverArtResolver cover_art_resolver; |
2901 | + }; |
2902 | |
2903 | - ServiceSkeleton(const CoverArtResolver& cover_art_resolver = always_missing_cover_art_resolver()); |
2904 | + ServiceSkeleton(const Configuration& configuration); |
2905 | ~ServiceSkeleton(); |
2906 | |
2907 | - // We keep track of all known player sessions here and render them accessible via |
2908 | - // the key. All of these functions are thread-safe but not reentrant. |
2909 | - // Returns true iff a player is known for the given key. |
2910 | - bool has_player_for_key(const Player::PlayerKey& key) const; |
2911 | - // Returns the player for the given key or throws std::out_of_range if no player is known |
2912 | - // for the given key. |
2913 | - std::shared_ptr<Player> player_for_key(const Player::PlayerKey& key) const; |
2914 | - // Enumerates all known players and invokes the given enumerator for each |
2915 | - // (key, player) pair. |
2916 | - void enumerate_players(const PlayerEnumerator& enumerator) const; |
2917 | - // Removes the player for the given key, and unsets it if it is the current one. |
2918 | - void remove_player_for_key(const Player::PlayerKey& key); |
2919 | - // Makes the player known under the given key current. |
2920 | - void set_current_player_for_key(const Player::PlayerKey& key); |
2921 | + // From media::Service |
2922 | + std::shared_ptr<Player> create_session(const Player::Configuration&); |
2923 | + std::shared_ptr<Player> create_fixed_session(const std::string& name, const Player::Configuration&); |
2924 | + std::shared_ptr<Player> resume_session(Player::PlayerKey); |
2925 | + void pause_other_sessions(Player::PlayerKey key); |
2926 | |
2927 | void run(); |
2928 | void stop(); |
2929 | |
2930 | === renamed directory 'src/core/media/call-monitor' => 'src/core/media/telephony' |
2931 | === modified file 'src/core/media/telephony/call_monitor.cpp' |
2932 | --- src/core/media/call-monitor/call_monitor.cpp 2015-01-12 21:35:36 +0000 |
2933 | +++ src/core/media/telephony/call_monitor.cpp 2015-03-16 15:38:27 +0000 |
2934 | @@ -29,8 +29,12 @@ |
2935 | #include <list> |
2936 | #include <mutex> |
2937 | |
2938 | +namespace media = core::ubuntu::media; |
2939 | |
2940 | -namespace { |
2941 | +namespace |
2942 | +{ |
2943 | +namespace impl |
2944 | +{ |
2945 | class TelepathyCallMonitor : public QObject |
2946 | { |
2947 | Q_OBJECT |
2948 | @@ -73,7 +77,7 @@ |
2949 | } |
2950 | } |
2951 | |
2952 | - void on_change(const std::function<void(CallMonitor::State)>& func) { |
2953 | + void on_change(const std::function<void(media::telephony::CallMonitor::State)>& func) { |
2954 | std::lock_guard<std::mutex> l(cb_lock); |
2955 | cb = func; |
2956 | } |
2957 | @@ -131,19 +135,19 @@ |
2958 | { |
2959 | std::lock_guard<std::mutex> l(cb_lock); |
2960 | if (cb) |
2961 | - cb(CallMonitor::OffHook); |
2962 | + cb(media::telephony::CallMonitor::State::OffHook); |
2963 | } |
2964 | |
2965 | void onHook() |
2966 | { |
2967 | std::lock_guard<std::mutex> l(cb_lock); |
2968 | if (cb) |
2969 | - cb(CallMonitor::OnHook); |
2970 | + cb(media::telephony::CallMonitor::State::OnHook); |
2971 | } |
2972 | |
2973 | private: |
2974 | std::mutex cb_lock; |
2975 | - std::function<void (CallMonitor::State)> cb; |
2976 | + std::function<void (media::telephony::CallMonitor::State)> cb; |
2977 | Tp::AccountManagerPtr mAccountManager; |
2978 | std::list<TelepathyCallMonitor*> mCallMonitors; |
2979 | |
2980 | @@ -159,63 +163,78 @@ |
2981 | mCallMonitors.push_back(tcm); |
2982 | } |
2983 | }; |
2984 | -} |
2985 | |
2986 | -class CallMonitorPrivate |
2987 | +struct CallMonitor : public media::telephony::CallMonitor |
2988 | { |
2989 | -public: |
2990 | - CallMonitorPrivate() { |
2991 | - mBridge = nullptr; |
2992 | - try { |
2993 | - std::thread([this]() { |
2994 | - qt::core::world::build_and_run(0, nullptr, [this]() { |
2995 | - qt::core::world::enter_with_task([this]() { |
2996 | + CallMonitor() : mBridge{nullptr} |
2997 | + { |
2998 | + try |
2999 | + { |
3000 | + qt_world = std::move(std::thread([this]() |
3001 | + { |
3002 | + qt::core::world::build_and_run(0, nullptr, [this]() |
3003 | + { |
3004 | + qt::core::world::enter_with_task([this]() |
3005 | + { |
3006 | std::cout << "CallMonitor: Creating TelepathyBridge" << std::endl; |
3007 | mBridge = new TelepathyBridge(); |
3008 | cv.notify_all(); |
3009 | }); |
3010 | }); |
3011 | - }).detach(); |
3012 | + })); |
3013 | } catch(const std::system_error& error) { |
3014 | std::cerr << "exception(std::system_error) in CallMonitor thread start" << error.what() << std::endl; |
3015 | } catch(...) { |
3016 | std::cerr << "exception(...) in CallMonitor thread start" << std::endl; |
3017 | } |
3018 | + |
3019 | // Wait until telepathy bridge is set, so we can hook up the change signals |
3020 | std::unique_lock<std::mutex> lck(mtx); |
3021 | cv.wait_for(lck, std::chrono::seconds(3)); |
3022 | + |
3023 | + if (mBridge) |
3024 | + { |
3025 | + mBridge->on_change([this](CallMonitor::State state) |
3026 | + { |
3027 | + call_state_changed(state); |
3028 | + }); |
3029 | + } |
3030 | } |
3031 | |
3032 | - ~CallMonitorPrivate() { |
3033 | + ~CallMonitor() |
3034 | + { |
3035 | + // We first clean up the bridge instance. |
3036 | + qt::core::world::enter_with_task([this]() |
3037 | + { |
3038 | + delete mBridge; |
3039 | + }).get(); |
3040 | + |
3041 | + // We then request destruction of the qt world. |
3042 | qt::core::world::destroy(); |
3043 | + |
3044 | + // Before we finally join the worker. |
3045 | + if (qt_world.joinable()) |
3046 | + qt_world.join(); |
3047 | + } |
3048 | + |
3049 | + const core::Signal<media::telephony::CallMonitor::State>& on_call_state_changed() const |
3050 | + { |
3051 | + return call_state_changed; |
3052 | } |
3053 | |
3054 | TelepathyBridge *mBridge; |
3055 | + core::Signal<media::telephony::CallMonitor::State> call_state_changed; |
3056 | |
3057 | -private: |
3058 | + std::thread qt_world; |
3059 | std::mutex mtx; |
3060 | std::condition_variable cv; |
3061 | }; |
3062 | - |
3063 | - |
3064 | -CallMonitor::CallMonitor(): |
3065 | - d(new CallMonitorPrivate) |
3066 | -{ |
3067 | -} |
3068 | - |
3069 | -CallMonitor::~CallMonitor() |
3070 | -{ |
3071 | - delete d->mBridge; |
3072 | - delete d; |
3073 | -} |
3074 | - |
3075 | -void CallMonitor::on_change(const std::function<void(CallMonitor::State)>& func) |
3076 | -{ |
3077 | - if (d->mBridge != nullptr) { |
3078 | - std::cout << "CallMonitor: Setting up callback for TelepathyBridge on_change" << std::endl; |
3079 | - d->mBridge->on_change(func); |
3080 | - } else |
3081 | - std::cerr << "TelepathyBridge: Failed to hook on_change signal, bridge not yet set" << std::endl; |
3082 | +} |
3083 | +} |
3084 | + |
3085 | +media::telephony::CallMonitor::Ptr media::telephony::make_platform_default_call_monitor() |
3086 | +{ |
3087 | + return std::make_shared<impl::CallMonitor>(); |
3088 | } |
3089 | |
3090 | #include "call_monitor.moc" |
3091 | |
3092 | === modified file 'src/core/media/telephony/call_monitor.h' |
3093 | --- src/core/media/call-monitor/call_monitor.h 2014-10-31 07:49:33 +0000 |
3094 | +++ src/core/media/telephony/call_monitor.h 2015-03-16 15:38:27 +0000 |
3095 | @@ -17,25 +17,50 @@ |
3096 | */ |
3097 | |
3098 | |
3099 | -#ifndef CALLMONITOR_H |
3100 | -#define CALLMONITOR_H |
3101 | +#ifndef CORE_UBUNTU_MEDIA_TELEPHONY_CALL_MONITOR_H_ |
3102 | +#define CORE_UBUNTU_MEDIA_TELEPHONY_CALL_MONITOR_H_ |
3103 | + |
3104 | +#include <core/signal.h> |
3105 | |
3106 | #include <functional> |
3107 | - |
3108 | -class CallMonitorPrivate; |
3109 | - |
3110 | -class CallMonitor |
3111 | -{ |
3112 | -public: |
3113 | - enum State { OffHook, OnHook }; |
3114 | - |
3115 | - CallMonitor(); |
3116 | - ~CallMonitor(); |
3117 | - |
3118 | - void on_change(const std::function<void(CallMonitor::State)>& func); |
3119 | - |
3120 | -private: |
3121 | - CallMonitorPrivate *d; |
3122 | +#include <memory> |
3123 | + |
3124 | +namespace core |
3125 | +{ |
3126 | +namespace ubuntu |
3127 | +{ |
3128 | +namespace media |
3129 | +{ |
3130 | +namespace telephony |
3131 | +{ |
3132 | +// CallMonitor models the ability to observe and react |
3133 | +// to changes of the overall call state of the system. |
3134 | +struct CallMonitor |
3135 | +{ |
3136 | + // Save us some typing. |
3137 | + typedef std::shared_ptr<CallMonitor> Ptr; |
3138 | + |
3139 | + // All known call states |
3140 | + enum class State |
3141 | + { |
3142 | + // No current call. |
3143 | + OffHook, |
3144 | + // Call in progress. |
3145 | + OnHook |
3146 | + }; |
3147 | + |
3148 | + CallMonitor() = default; |
3149 | + virtual ~CallMonitor() = default; |
3150 | + |
3151 | + // Emitted whenever the current call state of the system changes. |
3152 | + virtual const core::Signal<State>& on_call_state_changed() const = 0; |
3153 | }; |
3154 | |
3155 | -#endif // CALLMONITOR_H |
3156 | +// Returns a platform default implementation of CallMonitor. |
3157 | +CallMonitor::Ptr make_platform_default_call_monitor(); |
3158 | +} |
3159 | +} |
3160 | +} |
3161 | +} |
3162 | + |
3163 | +#endif // CORE_UBUNTU_MEDIA_TELEPHONY_CALL_MONITOR_H_ |
FAILED: Continuous integration, rev:121 jenkins. qa.ubuntu. com/job/ media-hub- ci/304/ jenkins. qa.ubuntu. com/job/ media-hub- vivid-amd64- ci/144/ console jenkins. qa.ubuntu. com/job/ media-hub- vivid-armhf- ci/144 jenkins. qa.ubuntu. com/job/ media-hub- vivid-armhf- ci/144/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ media-hub- vivid-i386- ci/144
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/media- hub-ci/ 304/rebuild
http://