Merge lp:~artem-anufrij/audience/upnp-client into lp:~audience-members/audience/trunk
- upnp-client
- Merge into trunk
Proposed by
Artem Anufrij
Status: | Needs review | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~artem-anufrij/audience/upnp-client | ||||
Merge into: | lp:~audience-members/audience/trunk | ||||
Diff against target: |
809 lines (+416/-90) 9 files modified
src/CMakeLists.txt (+9/-0) src/Objects/Video.vala (+113/-57) src/Services/LibraryManager.vala (+54/-11) src/Services/UPnPManager.vala (+173/-0) src/Widgets/Library/EpisodesPage.vala (+10/-4) src/Widgets/Library/LibraryItem.vala (+20/-10) src/Widgets/Library/LibraryPage.vala (+29/-7) src/Widgets/WelcomePage.vala (+7/-0) src/Window.vala (+1/-1) |
||||
To merge this branch: | bzr merge lp:~artem-anufrij/audience/upnp-client | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Zisu Andrei (community) | Needs Fixing | ||
Review via email: mp+310490@code.launchpad.net |
Commit message
Description of the change
UPnP-client support
To post a comment you must log in.
- 699. By Artem Anufrij
-
support Rygel connection
- 700. By Artem Anufrij
-
show welcomescreen button when UPnP items detected
Revision history for this message
Danielle Foré (danrabbit) wrote : | # |
Audience has migrated to GitHub. Please resubmit as a PR here: https:/
Unmerged revisions
- 700. By Artem Anufrij
-
show welcomescreen button when UPnP items detected
- 699. By Artem Anufrij
-
support Rygel connection
- 698. By Artem Anufrij
-
fixed poster size for plex items
- 697. By Artem Anufrij
-
plex support can be tested
- 696. By Artem Anufrij
-
switch from SEARCH to BROWSE --> Plex doesn't support SEARCH action
- 695. By Artem Anufrij
-
remove items from library if device is no more available
- 694. By Artem Anufrij
-
added files
- 693. By Artem Anufrij
-
video-x-
generic- symbolic for upnp items without poster - 692. By Artem Anufrij
-
development
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/CMakeLists.txt' |
2 | --- src/CMakeLists.txt 2016-11-02 23:59:41 +0000 |
3 | +++ src/CMakeLists.txt 2016-11-12 11:33:11 +0000 |
4 | @@ -19,6 +19,10 @@ |
5 | gstreamer-video-1.0 |
6 | gstreamer-tag-1.0 |
7 | clutter-gst-3.0 |
8 | + gupnp-1.0 |
9 | + gupnp-av-1.0 |
10 | + gssdp-1.0 |
11 | + libxml-2.0 |
12 | ) |
13 | |
14 | set (VALA_DEPS |
15 | @@ -30,6 +34,10 @@ |
16 | gstreamer-video-1.0 |
17 | gstreamer-tag-1.0 |
18 | clutter-gst-3.0 |
19 | + gupnp-1.0 |
20 | + gupnp-av-1.0 |
21 | + gssdp-1.0 |
22 | + libxml-2.0 |
23 | ) |
24 | |
25 | pkg_check_modules (DEPS REQUIRED ${PKG_DEPS}) |
26 | @@ -69,6 +77,7 @@ |
27 | Services/Inhibitor.vala |
28 | Services/DirictoryMonitoring.vala |
29 | Services/LibraryManager.vala |
30 | + Services/UPnPManager.vala |
31 | Services/Thubnailer.vala |
32 | |
33 | Objects/Video.vala |
34 | |
35 | === modified file 'src/Objects/Video.vala' |
36 | --- src/Objects/Video.vala 2016-09-27 20:26:37 +0000 |
37 | +++ src/Objects/Video.vala 2016-11-12 11:33:11 +0000 |
38 | @@ -1,4 +1,4 @@ |
39 | -/*- |
40 | + /*- |
41 | * Copyright (c) 2016-2016 elementary LLC. |
42 | * |
43 | * This program is free software: you can redistribute it and/or modify |
44 | @@ -19,6 +19,9 @@ |
45 | */ |
46 | |
47 | namespace Audience.Objects { |
48 | + |
49 | + public enum LibraryVideoType { LOCAL, UPNP } |
50 | + |
51 | public class Video : Object { |
52 | Audience.Services.LibraryManager manager; |
53 | |
54 | @@ -27,18 +30,12 @@ |
55 | public signal void thumbnail_changed (); |
56 | public signal void trashed (); |
57 | |
58 | - public File video_file { get; private set; } |
59 | - public string directory { get; construct set; } |
60 | - public string file { get; construct set; } |
61 | - |
62 | - public string title { get; private set; } |
63 | + public string title { get; construct set; } |
64 | public int year { get; private set; default = -1;} |
65 | |
66 | public Gdk.Pixbuf? poster { get; private set; default = null; } |
67 | - public Gdk.Pixbuf? thumbnail { get; private set; default = null; } |
68 | |
69 | public string mime_type { get; construct set; } |
70 | - public string poster_cache_file { get; private set; } |
71 | |
72 | public string hash_file_poster { get; construct set; } |
73 | public string hash_episode_poster { get; construct set; } |
74 | @@ -47,17 +44,33 @@ |
75 | |
76 | public string container { get; construct set; default = ""; } |
77 | |
78 | - public Video (string directory, string file, string mime_type) { |
79 | - Object (directory: directory, file: file, mime_type: mime_type); |
80 | - } |
81 | - |
82 | - construct { |
83 | - manager = Audience.Services.LibraryManager.get_instance (); |
84 | - manager.thumbler.finished.connect (dbus_finished); |
85 | - |
86 | - title = Audience.get_title (file); |
87 | - |
88 | - extract_metadata (); |
89 | + // Local |
90 | + public File video_file { get; private set; } |
91 | + public string directory { get; construct set; } |
92 | + public string file { get; construct set; } |
93 | + public Gdk.Pixbuf? thumbnail { get; private set; default = null; } |
94 | + public string poster_cache_file { get; private set; } |
95 | + |
96 | + // UPnP |
97 | + public string video_uri { get; construct set; default = "";} |
98 | + public string poster_uri { get; construct set; } |
99 | + |
100 | + |
101 | + public LibraryVideoType library_video_type { get; private set; } |
102 | + |
103 | + public Video.local_media (string directory, string file, string mime_type) { |
104 | + Object (title: Audience.get_title (file), directory: directory, file: file, mime_type: mime_type); |
105 | + init_local (); |
106 | + } |
107 | + |
108 | + public Video.upnp_media (string container, string title, string video_uri, string poster_uri, string mime_type) { |
109 | + Object (container: container, title: title, video_uri: video_uri, poster_uri: poster_uri, mime_type: mime_type); |
110 | + init_upnp (); |
111 | + } |
112 | + |
113 | + private void init_local () { |
114 | + library_video_type = LibraryVideoType.LOCAL; |
115 | + |
116 | video_file = File.new_for_path (this.get_path ()); |
117 | |
118 | if (directory != Audience.settings.library_folder) { |
119 | @@ -70,6 +83,20 @@ |
120 | thumbnail_large_path = Path.build_filename (GLib.Environment.get_user_cache_dir (),"thumbnails", "large", hash_file_poster + ".png"); |
121 | thumbnail_normal_path = Path.build_filename (GLib.Environment.get_user_cache_dir (),"thumbnails", "normal", hash_file_poster + ".png"); |
122 | poster_cache_file = Path.build_filename (App.get_instance ().get_cache_directory (), hash_file_poster + ".jpg"); |
123 | + } |
124 | + |
125 | + private void init_upnp () { |
126 | + library_video_type = LibraryVideoType.UPNP; |
127 | + video_file = File.new_for_uri (video_uri); |
128 | + |
129 | + hash_file_poster = GLib.Checksum.compute_for_string (ChecksumType.MD5, video_file.get_uri (), video_file.get_uri ().length); |
130 | + thumbnail_large_path = Path.build_filename (GLib.Environment.get_user_cache_dir (),"thumbnails", "large", hash_file_poster + ".png"); |
131 | + thumbnail_normal_path = Path.build_filename (GLib.Environment.get_user_cache_dir (),"thumbnails", "normal", hash_file_poster + ".png"); |
132 | + } |
133 | + |
134 | + construct { |
135 | + manager = Audience.Services.LibraryManager.get_instance (); |
136 | + manager.thumbler.finished.connect (dbus_finished); |
137 | |
138 | notify["poster"].connect (() => { |
139 | poster_changed (this); |
140 | @@ -80,6 +107,8 @@ |
141 | notify["thumbnail"].connect (() => { |
142 | thumbnail_changed (); |
143 | }); |
144 | + |
145 | + extract_metadata (); |
146 | } |
147 | |
148 | private void extract_metadata () { |
149 | @@ -87,8 +116,9 @@ |
150 | MatchInfo info; |
151 | if (manager.regex_year.match (this.title, 0, out info)) { |
152 | year = int.parse (info.fetch (0).substring (1, 4)); |
153 | - title = this.title.replace (info.fetch (0) + ")", "").strip (); |
154 | + title = title.replace (info.fetch (0) + ")", "").replace ("_", " ").strip (); |
155 | } |
156 | + title = title.replace ("_", " ").strip (); |
157 | } |
158 | |
159 | public async void initialize_poster () { |
160 | @@ -103,47 +133,73 @@ |
161 | Gdk.Pixbuf? pixbuf = null; |
162 | |
163 | ThreadFunc<void*> run = () => { |
164 | - if (!FileUtils.test (thumbnail_large_path, FileTest.EXISTS) || !FileUtils.test (thumbnail_normal_path, FileTest.EXISTS)) { |
165 | - // Call DBUS for create a new THUMBNAIL |
166 | - Gee.ArrayList<string> uris = new Gee.ArrayList<string> (); |
167 | - Gee.ArrayList<string> mimes = new Gee.ArrayList<string> (); |
168 | - |
169 | - uris.add (video_file.get_uri ()); |
170 | - mimes.add (mime_type); |
171 | - |
172 | - manager.thumbler.Instand (uris, mimes, "large"); |
173 | - manager.thumbler.Instand (uris, mimes, "normal"); |
174 | - } |
175 | - |
176 | - string? poster_path = poster_cache_file; |
177 | - pixbuf = manager.get_poster_from_file (poster_path); |
178 | - |
179 | - // POSTER in Cache exists |
180 | - if (pixbuf != null) { |
181 | - Idle.add ((owned) callback); |
182 | - return null; |
183 | - } |
184 | - |
185 | - poster_path = get_native_poster_path (); |
186 | - if (poster_path != null) { |
187 | - pixbuf = manager.get_poster_from_file (poster_path); |
188 | - } |
189 | - |
190 | - // POSTER found |
191 | - if (pixbuf != null) { |
192 | + if (library_video_type == LibraryVideoType.LOCAL) { |
193 | + if (!FileUtils.test (thumbnail_large_path, FileTest.EXISTS) || !FileUtils.test (thumbnail_normal_path, FileTest.EXISTS)) { |
194 | + // Call DBUS for create a new THUMBNAIL |
195 | + Gee.ArrayList<string> uris = new Gee.ArrayList<string> (); |
196 | + Gee.ArrayList<string> mimes = new Gee.ArrayList<string> (); |
197 | + uris.add (video_file.get_uri ()); |
198 | + mimes.add (mime_type); |
199 | + |
200 | + manager.thumbler.Instand (uris, mimes, "large"); |
201 | + manager.thumbler.Instand (uris, mimes, "normal"); |
202 | + } |
203 | + |
204 | + string? poster_path = poster_cache_file; |
205 | + pixbuf = manager.get_poster_from_file (poster_path); |
206 | + |
207 | + // POSTER in Cache exists |
208 | + if (pixbuf != null) { |
209 | + Idle.add ((owned) callback); |
210 | + return null; |
211 | + } |
212 | + |
213 | + poster_path = get_native_poster_path (); |
214 | + if (poster_path != null) { |
215 | + pixbuf = manager.get_poster_from_file (poster_path); |
216 | + } |
217 | + |
218 | + // POSTER found |
219 | + if (pixbuf != null) { |
220 | + try { |
221 | + pixbuf.save (poster_cache_file, "jpeg"); |
222 | + } catch (Error e) { |
223 | + warning (e.message); |
224 | + } |
225 | + Idle.add ((owned) callback); |
226 | + return null; |
227 | + } |
228 | + |
229 | + if (FileUtils.test (thumbnail_large_path, FileTest.EXISTS)) { |
230 | + pixbuf = manager.get_poster_from_file (thumbnail_large_path); |
231 | + Idle.add ((owned) callback); |
232 | + return null; |
233 | + } |
234 | + } else if (library_video_type == LibraryVideoType.UPNP) { |
235 | + string? poster_path = poster_uri; |
236 | + pixbuf = manager.get_poster_from_file (poster_path); |
237 | + |
238 | + // UPnP POSTER exists |
239 | + if (pixbuf != null) { |
240 | + Idle.add ((owned) callback); |
241 | + return null; |
242 | + } |
243 | + var p = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, Audience.Services.POSTER_WIDTH, Audience.Services.POSTER_HEIGHT); |
244 | try { |
245 | - pixbuf.save (poster_cache_file, "jpeg"); |
246 | + pixbuf = Gtk.IconTheme.get_default().load_icon ("video-x-generic-symbolic", 64, Gtk.IconLookupFlags.USE_BUILTIN); |
247 | } catch (Error e) { |
248 | warning (e.message); |
249 | } |
250 | - Idle.add ((owned) callback); |
251 | - return null; |
252 | - } |
253 | - |
254 | - if (FileUtils.test (thumbnail_large_path, FileTest.EXISTS)) { |
255 | - pixbuf = manager.get_poster_from_file (thumbnail_large_path); |
256 | - Idle.add ((owned) callback); |
257 | - return null; |
258 | + pixbuf.scale (p, 0, 0, Audience.Services.POSTER_WIDTH, Audience.Services.POSTER_HEIGHT, |
259 | + (Audience.Services.POSTER_WIDTH - 64) / 2, |
260 | + (Audience.Services.POSTER_HEIGHT - 64) / 2 - 1, 1, 1, Gdk.InterpType.HYPER); |
261 | + |
262 | + pixbuf = p; |
263 | + |
264 | + if (pixbuf != null) { |
265 | + Idle.add ((owned) callback); |
266 | + return null; |
267 | + } |
268 | } |
269 | |
270 | Idle.add ((owned) callback); |
271 | |
272 | === modified file 'src/Services/LibraryManager.vala' |
273 | --- src/Services/LibraryManager.vala 2016-09-27 20:26:37 +0000 |
274 | +++ src/Services/LibraryManager.vala 2016-11-12 11:33:11 +0000 |
275 | @@ -129,7 +129,7 @@ |
276 | if (name == "") { |
277 | name = file_info.get_name (); |
278 | } |
279 | - var video = new Audience.Objects.Video (source, name, file_info.get_content_type ()); |
280 | + var video = new Audience.Objects.Video.local_media (source, name, file_info.get_content_type ()); |
281 | video_file_detected (video); |
282 | poster_hash.add (video.hash_file_poster + ".jpg"); |
283 | poster_hash.add (video.hash_episode_poster + ".jpg"); |
284 | @@ -194,22 +194,65 @@ |
285 | |
286 | public Gdk.Pixbuf? get_poster_from_file (string poster_path) { |
287 | Gdk.Pixbuf? pixbuf = null; |
288 | - if (FileUtils.test (poster_path, FileTest.EXISTS)) { |
289 | - try { |
290 | - pixbuf = new Gdk.Pixbuf.from_file_at_scale (poster_path, -1, Audience.Services.POSTER_HEIGHT, true); |
291 | - } catch (Error e) { |
292 | - warning (e.message); |
293 | - } |
294 | - if (pixbuf == null) { |
295 | - return null; |
296 | - } |
297 | - // Cut THUMBNAIL images |
298 | + |
299 | + if (poster_path.has_prefix ("http")) { |
300 | + var http_file = File.new_for_uri (poster_path); |
301 | + FileInputStream http_input; |
302 | + try { |
303 | + http_input = http_file.read (); |
304 | + } catch (Error e) { |
305 | + warning ("Unable to fetch '%s': %s", poster_path, e.message); |
306 | + return null; |
307 | + } |
308 | + |
309 | + var loader = new Gdk.PixbufLoader (); |
310 | + var buf = new uint8[4096]; |
311 | + for (;;) { |
312 | + ssize_t bytes_read; |
313 | + try { |
314 | + bytes_read = http_input.read (buf); |
315 | + if (bytes_read == 0) |
316 | + break; |
317 | + } catch (IOError e) { |
318 | + warning ("Unable to read data from '%s': %s", poster_path, e.message); |
319 | + return null; |
320 | + } |
321 | + try { |
322 | + loader.write (buf[0:bytes_read]); |
323 | + } catch (Error e) { |
324 | + warning ("Unable to parse image from '%s': %s", poster_path, e.message); |
325 | + return null; |
326 | + } |
327 | + } |
328 | + try { |
329 | + loader.close (); |
330 | + } catch (Error e) { |
331 | + warning ("Unable to parse image from '%s': %s", poster_path, e.message); |
332 | + return null; |
333 | + } |
334 | + pixbuf = loader.get_pixbuf (); |
335 | + } else { |
336 | + if (poster_path != "" && FileUtils.test (poster_path, FileTest.EXISTS)) { |
337 | + try { |
338 | + pixbuf = new Gdk.Pixbuf.from_file_at_scale (poster_path, -1, Audience.Services.POSTER_HEIGHT, true); |
339 | + } catch (Error e) { |
340 | + warning (e.message); |
341 | + } |
342 | + if (pixbuf == null) { |
343 | + return null; |
344 | + } |
345 | + } |
346 | + } |
347 | + |
348 | + // Cut THUMBNAIL images |
349 | + if (pixbuf != null) { |
350 | int width = pixbuf.width; |
351 | if (width > Audience.Services.POSTER_WIDTH) { |
352 | int x_offset = (width - Audience.Services.POSTER_WIDTH) / 2; |
353 | pixbuf = new Gdk.Pixbuf.subpixbuf (pixbuf, x_offset, 0, Audience.Services.POSTER_WIDTH, Audience.Services.POSTER_HEIGHT); |
354 | } |
355 | } |
356 | + |
357 | return pixbuf; |
358 | } |
359 | } |
360 | |
361 | === added file 'src/Services/UPnPManager.vala' |
362 | --- src/Services/UPnPManager.vala 1970-01-01 00:00:00 +0000 |
363 | +++ src/Services/UPnPManager.vala 2016-11-12 11:33:11 +0000 |
364 | @@ -0,0 +1,173 @@ |
365 | +// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- |
366 | +/*- |
367 | + * Copyright (c) 2016-2016 elementary LLC. |
368 | + * |
369 | + * This program is free software: you can redistribute it and/or modify |
370 | + * it under the terms of the GNU General Public License as published by |
371 | + * the Free Software Foundation, either version 3 of the License, or |
372 | + * (at your option) any later version. |
373 | + |
374 | + * This program is distributed in the hope that it will be useful, |
375 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
376 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
377 | + * GNU General Public License for more details. |
378 | + |
379 | + * You should have received a copy of the GNU General Public License |
380 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
381 | + * |
382 | + * Authored by: Artem Anufrij <artem.anufrij@live.de> |
383 | + * |
384 | + */ |
385 | + |
386 | +namespace Audience.Services { |
387 | + public class UPnPManager : Object { |
388 | + |
389 | + public signal void video_item_detected (Audience.Objects.Video video); |
390 | + public signal void sevice_unavailable (string device_uri); |
391 | + |
392 | + GUPnP.Context context; |
393 | + GUPnP.ControlPoint point; |
394 | + |
395 | + public static UPnPManager instance = null; |
396 | + public static UPnPManager get_instance () { |
397 | + if (instance == null) { |
398 | + instance = new UPnPManager (); |
399 | + } |
400 | + |
401 | + return instance; |
402 | + } |
403 | + |
404 | + private UPnPManager () {} |
405 | + |
406 | + construct { |
407 | + context = new GUPnP.Context(null, null, 0); |
408 | + |
409 | + point = new GUPnP.ControlPoint(context, "urn:schemas-upnp-org:device:MediaServer:1"); |
410 | + point.device_proxy_unavailable.connect ((device_proxy) => { |
411 | + debug ("DEVICE OFF: %s", device_proxy.get_friendly_name ()); |
412 | + string device_uri = device_proxy.get_presentation_url (); |
413 | + sevice_unavailable (device_uri); |
414 | + }); |
415 | + point.device_proxy_available.connect ((device_proxy) => { |
416 | + debug ("DEVICE ON: %s", device_proxy.get_friendly_name ()); |
417 | + foreach (var si in device_proxy.list_services ()) { |
418 | + if (si.service_type.has_prefix("urn:schemas-upnp-org:service:ContentDirectory") && si is GUPnP.ServiceProxy) { |
419 | + var proxy = si as GUPnP.ServiceProxy; |
420 | + try { |
421 | + Gee.ArrayList<string> items = new Gee.ArrayList<string> (); |
422 | + expand_content (proxy, "0", items, "root"); |
423 | + } catch (Error e) { |
424 | + warning (e.message); |
425 | + } |
426 | + } |
427 | + } |
428 | + debug ("DEVICE SCAN FINISHED: %s", device_proxy.get_friendly_name ()); |
429 | + }); |
430 | + } |
431 | + |
432 | + private void expand_content (GUPnP.ServiceProxy proxy, string object_id, Gee.ArrayList<string> items, string parent) { |
433 | + // SEARCH PARAMETER IN |
434 | + List<string> in_names = new List<string> (); |
435 | + List<unowned Value?> in_values = new List<unowned Value?> (); |
436 | + in_names.append ("ObjectID"); |
437 | + in_names.append ("BrowseFlag"); |
438 | + in_names.append ("StartingIndex"); |
439 | + in_names.append ("RequestedCount"); |
440 | + in_names.append ("Filter"); |
441 | + in_names.append ("SortCriteria"); |
442 | + var val_object_id = Value (typeof (string)); |
443 | + val_object_id.set_string (object_id); |
444 | + var val_browse_crit = Value (typeof (string)); |
445 | + val_browse_crit.set_string ("BrowseDirectChildren"); |
446 | + var val_start_index = Value (typeof (int)); |
447 | + val_start_index.set_int (0); |
448 | + var val_req_count = Value (typeof (int)); |
449 | + val_req_count.set_int (0); |
450 | + var val_filter = Value (typeof (string)); |
451 | + val_filter.set_string ("*"); |
452 | + var val_sort_crit = Value (typeof (string)); |
453 | + val_sort_crit.set_string (""); |
454 | + in_values.append (val_object_id); |
455 | + in_values.append (val_browse_crit); |
456 | + in_values.append (val_start_index); |
457 | + in_values.append (val_req_count); |
458 | + in_values.append (val_filter); |
459 | + in_values.append (val_sort_crit); |
460 | + |
461 | + // SEARCH PARAMETER OUT |
462 | + List<string> out_names = new List<string> (); |
463 | + List<Type> out_types = new List<Type> (); |
464 | + List<Value*> out_values = new List<Value*> (); |
465 | + out_names.append ("Result"); |
466 | + out_types.append (typeof (string)); |
467 | + |
468 | + proxy.send_action_list ("Browse", in_names, in_values, out_names, out_types, out out_values); |
469 | + |
470 | + var parser = new GUPnP.DIDLLiteParser (); |
471 | + parser.container_available.connect ((container) => { |
472 | + if (container.id != object_id && (container.parent_id == object_id || object_id == "0")) { |
473 | + expand_content (proxy, container.id, items, container.title); |
474 | + } |
475 | + }); |
476 | + parser.item_available.connect ((item) => { |
477 | + crate_upnp_object (item, items, parent); |
478 | + }); |
479 | + |
480 | + // PARSE ITEMS |
481 | + foreach (Value* v in out_values) { |
482 | + string output = v.get_string(); |
483 | + v.unset(); |
484 | + GLib.Slice.free (sizeof(Value), v); |
485 | + try { |
486 | + GUPnP.MediaCollection media_collection = new GUPnP.MediaCollection.from_string (output); |
487 | + if (media_collection.get_items ().length () > 0) { |
488 | + foreach (var item in media_collection.get_items ()) { |
489 | + crate_upnp_object (item, items, parent); |
490 | + } |
491 | + } else { |
492 | + parser.parse_didl (output); |
493 | + } |
494 | + |
495 | + } catch (Error e) { |
496 | + //PARSTER THROWS AN EXCEPTION IF VALUE IS EMPTY |
497 | + //IT CAN BE IGNORED |
498 | + debug (e.message); |
499 | + } |
500 | + } |
501 | + } |
502 | + |
503 | + private void crate_upnp_object (GUPnP.DIDLLiteObject item, Gee.ArrayList<string> items, string parent) { |
504 | + if (item.upnp_class.has_prefix("object.item.videoItem")) { |
505 | + string video_uri = ""; |
506 | + string poster_uri = ""; |
507 | + string mime_type = ""; |
508 | + |
509 | + foreach (var res in item.get_resources ()) { |
510 | + if (res.protocol_info.mime_type.has_prefix ("video")) { |
511 | + video_uri = res.uri; |
512 | + mime_type = res.protocol_info.mime_type; |
513 | + } else if (res.protocol_info.mime_type.has_prefix ("image")) { |
514 | + poster_uri = res.uri; |
515 | + } |
516 | + } |
517 | + |
518 | + if (poster_uri == "" && item.album_art != null) { |
519 | + poster_uri = item.album_art; |
520 | + } |
521 | + |
522 | + if (video_uri != "" && !items.contains (video_uri)) { |
523 | + items.add (video_uri); |
524 | + video_item_detected (new Audience.Objects.Video.upnp_media (parent, item.title, video_uri, poster_uri, mime_type)); |
525 | + } |
526 | + } |
527 | + } |
528 | + |
529 | + public bool active { |
530 | + get { |
531 | + return point.active; |
532 | + } set { |
533 | + point.active = value; |
534 | + } |
535 | + } |
536 | + } |
537 | +} |
538 | |
539 | === modified file 'src/Widgets/Library/EpisodesPage.vala' |
540 | --- src/Widgets/Library/EpisodesPage.vala 2016-11-02 23:59:41 +0000 |
541 | +++ src/Widgets/Library/EpisodesPage.vala 2016-11-12 11:33:11 +0000 |
542 | @@ -27,6 +27,7 @@ |
543 | Granite.Widgets.AlertView alert_view; |
544 | |
545 | public Audience.Services.LibraryManager manager; |
546 | + public Audience.Services.UPnPManager upnp; |
547 | |
548 | string query; |
549 | |
550 | @@ -65,6 +66,9 @@ |
551 | manager = Audience.Services.LibraryManager.get_instance (); |
552 | manager.video_file_deleted.connect (remove_item_from_path); |
553 | manager.video_file_detected.connect (add_item); |
554 | + |
555 | + upnp = Audience.Services.UPnPManager.get_instance (); |
556 | + upnp.sevice_unavailable.connect (remove_item_from_path); |
557 | } |
558 | |
559 | public void set_episodes_items (Gee.ArrayList<Audience.Objects.Video> episodes) { |
560 | @@ -80,6 +84,7 @@ |
561 | private void play_video (Gtk.FlowBoxChild item) { |
562 | var selected = (item as Audience.LibraryItem); |
563 | var video = selected.episodes.first (); |
564 | + debug (video.video_file.get_uri () + " - " + video.video_file.query_exists ().to_string ()); |
565 | if (video.video_file.query_exists ()) { |
566 | bool from_beginning = video.video_file.get_uri () != settings.current_video; |
567 | App.get_instance ().mainwindow.play_file (video.video_file.get_uri (), from_beginning); |
568 | @@ -102,10 +107,9 @@ |
569 | } |
570 | |
571 | string[] filter_elements = query.split (" "); |
572 | - var video_title = (child as LibraryItem).get_title (); |
573 | |
574 | foreach (string filter_element in filter_elements) { |
575 | - if (!video_title.down ().contains (filter_element.down ())) { |
576 | + if (!(child as LibraryItem).title.down ().contains (filter_element.down ())) { |
577 | return false; |
578 | } |
579 | } |
580 | @@ -116,7 +120,7 @@ |
581 | var item1 = (LibraryItem)child1; |
582 | var item2 = (LibraryItem)child2; |
583 | if (item1 != null && item2 != null) { |
584 | - return item1.episodes.first ().file.collate (item2.episodes.first ().file); |
585 | + return item1.episodes.first ().title.collate (item2.episodes.first ().title); |
586 | } |
587 | return 0; |
588 | } |
589 | @@ -132,7 +136,9 @@ |
590 | |
591 | private async void remove_item_from_path (string path ) { |
592 | foreach (var child in view_episodes.get_children ()) { |
593 | - if ((child as LibraryItem).episodes.size == 0 || (child as LibraryItem).episodes.first ().video_file.get_path ().has_prefix (path)) { |
594 | + if ((child as LibraryItem).episodes.size == 0 |
595 | + || (child as LibraryItem).episodes.first ().video_file.get_uri ().has_prefix (path) |
596 | + || (child as LibraryItem).episodes.first ().video_file.get_path ().has_prefix (path)) { |
597 | child.dispose (); |
598 | } |
599 | } |
600 | |
601 | === modified file 'src/Widgets/Library/LibraryItem.vala' |
602 | --- src/Widgets/Library/LibraryItem.vala 2016-11-02 23:59:41 +0000 |
603 | +++ src/Widgets/Library/LibraryItem.vala 2016-11-12 11:33:11 +0000 |
604 | @@ -50,9 +50,13 @@ |
605 | video.title_changed.connect (video_title_changed); |
606 | video.poster_changed.connect (video_poster_changed); |
607 | |
608 | - hash = GLib.Checksum.compute_for_string (ChecksumType.MD5, video.video_file.get_parent ().get_uri (), video.video_file.get_parent ().get_uri ().length); |
609 | - episode_poster_path = Path.build_filename (video.video_file.get_parent ().get_path (), video.video_file.get_parent ().get_basename () + ".jpg"); |
610 | - poster_cache_file = Path.build_filename (App.get_instance ().get_cache_directory (), hash + ".jpg"); |
611 | + if (video.library_video_type == Audience.Objects.LibraryVideoType.LOCAL) { |
612 | + hash = GLib.Checksum.compute_for_string (ChecksumType.MD5, video.video_file.get_parent ().get_uri (), video.video_file.get_parent ().get_uri ().length); |
613 | + episode_poster_path = Path.build_filename (video.video_file.get_parent ().get_path (), video.video_file.get_parent ().get_basename () + ".jpg"); |
614 | + poster_cache_file = Path.build_filename (App.get_instance ().get_cache_directory (), hash + ".jpg"); |
615 | + } else if (video.library_video_type == Audience.Objects.LibraryVideoType.UPNP) { |
616 | + poster_cache_file = video.poster_uri; |
617 | + } |
618 | } |
619 | |
620 | construct { |
621 | @@ -141,7 +145,9 @@ |
622 | } |
623 | |
624 | private bool show_context_menu (Gtk.Widget sender, Gdk.EventButton evt) { |
625 | - if (evt.type == Gdk.EventType.BUTTON_PRESS && evt.button == 3) { |
626 | + if (episodes.first ().library_video_type == Audience.Objects.LibraryVideoType.LOCAL |
627 | + && evt.type == Gdk.EventType.BUTTON_PRESS |
628 | + && evt.button == 3) { |
629 | context_menu.popup (null, null, null, evt.button, evt.time); |
630 | return true; |
631 | } |
632 | @@ -156,9 +162,9 @@ |
633 | |
634 | private void video_title_changed (Audience.Objects.Video video) { |
635 | if (episodes.size == 1) { |
636 | - title_label.label = video.title; |
637 | + title = video.title; |
638 | } else { |
639 | - title_label.label = video.container; |
640 | + title = video.container; |
641 | } |
642 | title_label.show (); |
643 | } |
644 | @@ -220,15 +226,19 @@ |
645 | }); |
646 | episodes.add (episode); |
647 | if (episodes.size == 1) { |
648 | - title_label.label = episode.title; |
649 | + title = episode.title; |
650 | } else if (episodes.size == 2) { |
651 | - title_label.label = episode.container; |
652 | + title = episode.container; |
653 | create_episode_poster (); |
654 | } |
655 | } |
656 | |
657 | - public string get_title () { |
658 | - return title_label.label; |
659 | + public string title { |
660 | + get { |
661 | + return title_label.label; |
662 | + } set { |
663 | + title_label.label = value; |
664 | + } |
665 | } |
666 | |
667 | public void create_episode_poster () { |
668 | |
669 | === modified file 'src/Widgets/Library/LibraryPage.vala' |
670 | --- src/Widgets/Library/LibraryPage.vala 2016-11-02 23:59:41 +0000 |
671 | +++ src/Widgets/Library/LibraryPage.vala 2016-11-12 11:33:11 +0000 |
672 | @@ -27,6 +27,7 @@ |
673 | |
674 | public Gtk.FlowBox view_movies; |
675 | public Audience.Services.LibraryManager manager; |
676 | + public Audience.Services.UPnPManager upnp; |
677 | public Gtk.ScrolledWindow scrolled_window; |
678 | bool poster_initialized = false; |
679 | string query; |
680 | @@ -45,7 +46,7 @@ |
681 | |
682 | construct { |
683 | manager = Audience.Services.LibraryManager.get_instance (); |
684 | - |
685 | + upnp = Audience.Services.UPnPManager.get_instance (); |
686 | query = ""; |
687 | |
688 | scrolled_window = new Gtk.ScrolledWindow (null, null); |
689 | @@ -62,7 +63,16 @@ |
690 | |
691 | scrolled_window.add (view_movies); |
692 | |
693 | - manager = Audience.Services.LibraryManager.get_instance (); |
694 | + upnp.sevice_unavailable.connect ((device_uri) => { |
695 | + foreach (var child in view_movies.get_children ()) { |
696 | + foreach (var video in (child as LibraryItem).episodes) { |
697 | + if (video.video_uri != null && video.video_uri.has_prefix(device_uri)) { |
698 | + remove_item.begin (child as LibraryItem); |
699 | + } |
700 | + } |
701 | + } |
702 | + }); |
703 | + |
704 | manager.video_file_detected.connect (add_item); |
705 | manager.video_file_deleted.connect (remove_item_from_path); |
706 | manager.video_moved_to_trash.connect ((video) => { |
707 | @@ -71,6 +81,10 @@ |
708 | |
709 | manager.begin_scan (); |
710 | |
711 | + upnp.video_item_detected.connect (add_item); |
712 | + upnp.active = true; |
713 | + |
714 | + |
715 | map.connect (() => { |
716 | if (!poster_initialized) { |
717 | poster_initialized = true; |
718 | @@ -89,7 +103,11 @@ |
719 | |
720 | if (selected.episodes.size == 1) { |
721 | bool from_beginning = selected.episodes.first ().video_file.get_uri () != settings.current_video; |
722 | - App.get_instance ().mainwindow.play_file (selected.episodes.first ().video_file.get_uri (), from_beginning); |
723 | + if (selected.episodes.first ().library_video_type == Audience.Objects.LibraryVideoType.LOCAL) { |
724 | + App.get_instance ().mainwindow.play_file (selected.episodes.first ().video_file.get_uri (), from_beginning); |
725 | + } else if (selected.episodes.first ().library_video_type == Audience.Objects.LibraryVideoType.UPNP) { |
726 | + App.get_instance ().mainwindow.play_file (selected.episodes.first ().video_uri, from_beginning); |
727 | + } |
728 | } else { |
729 | last_filter = query; |
730 | show_episodes (selected); |
731 | @@ -100,6 +118,9 @@ |
732 | foreach (var child in view_movies.get_children ()) { |
733 | if (video.container != null && (child as LibraryItem).episodes.first ().container == video.container) { |
734 | (child as LibraryItem).add_episode (video); |
735 | + if ((child as LibraryItem).episodes.size == 2) { |
736 | + view_movies.invalidate_sort (); |
737 | + } |
738 | return; |
739 | } |
740 | } |
741 | @@ -113,7 +134,9 @@ |
742 | |
743 | private async void remove_item (LibraryItem item) { |
744 | foreach (var video in item.episodes) { |
745 | - manager.clear_cache.begin (video.poster_cache_file); |
746 | + if (video.poster_cache_file != null) { |
747 | + manager.clear_cache.begin (video.poster_cache_file); |
748 | + } |
749 | } |
750 | item.dispose (); |
751 | } |
752 | @@ -142,10 +165,9 @@ |
753 | } |
754 | |
755 | string[] filter_elements = query.split (" "); |
756 | - var video_title = (child as LibraryItem).get_title (); |
757 | |
758 | foreach (string filter_element in filter_elements) { |
759 | - if (!video_title.down ().contains (filter_element.down ())) { |
760 | + if (!(child as LibraryItem).title.down ().contains (filter_element.down ())) { |
761 | return false; |
762 | } |
763 | } |
764 | @@ -156,7 +178,7 @@ |
765 | var item1 = (LibraryItem)child1; |
766 | var item2 = (LibraryItem)child2; |
767 | if (item1 != null && item2 != null) { |
768 | - return item1.get_title ().collate (item2.get_title ()); |
769 | + return item1.title.collate (item2.title); |
770 | } |
771 | return 0; |
772 | } |
773 | |
774 | === modified file 'src/Widgets/WelcomePage.vala' |
775 | --- src/Widgets/WelcomePage.vala 2016-09-28 02:32:33 +0000 |
776 | +++ src/Widgets/WelcomePage.vala 2016-11-12 11:33:11 +0000 |
777 | @@ -2,6 +2,7 @@ |
778 | public class WelcomePage : Granite.Widgets.Welcome { |
779 | private DiskManager disk_manager; |
780 | private Services.LibraryManager library_manager; |
781 | + private Services.UPnPManager upnp_manager; |
782 | public WelcomePage () { |
783 | base (_("No Videos Open"), _("Select a source to begin playing.")); |
784 | } |
785 | @@ -44,6 +45,12 @@ |
786 | set_item_visible (3, LibraryPage.get_instance ().has_items); |
787 | }); |
788 | |
789 | + upnp_manager = Services.UPnPManager.get_instance (); |
790 | + upnp_manager.video_item_detected.connect ((vid) => { |
791 | + set_item_visible (3, true); |
792 | + this.show_all (); |
793 | + }); |
794 | + |
795 | append ("media-cdrom", _("Play from Disc"), _("Watch a DVD or open a file from disc")); |
796 | set_item_visible (2, disk_manager.has_media_volumes ()); |
797 | |
798 | |
799 | === modified file 'src/Window.vala' |
800 | --- src/Window.vala 2016-09-27 19:58:34 +0000 |
801 | +++ src/Window.vala 2016-11-12 11:33:11 +0000 |
802 | @@ -106,7 +106,7 @@ |
803 | episodes_page.set_episodes_items (item.episodes); |
804 | episodes_page.poster.pixbuf = item.poster.pixbuf; |
805 | main_stack.set_visible_child (episodes_page); |
806 | - this.title = item.get_title (); |
807 | + this.title = item.title; |
808 | search_entry.text = ""; |
809 | }); |
810 |
AFAICT looks good. Some review inline.