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