Merge lp:~artem-anufrij/audience/upnp-client into lp:~audience-members/audience/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
Reviewer Review Type Date Requested Status
Zisu Andrei (community) Needs Fixing
Review via email: mp+310490@code.launchpad.net

Description of the change

UPnP-client support

To post a comment you must log in.
Revision history for this message
Zisu Andrei (matzipan) wrote :

AFAICT looks good. Some review inline.

review: Needs Fixing
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://github.com/elementary/videos

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
=== modified file 'src/CMakeLists.txt'
--- src/CMakeLists.txt 2016-11-02 23:59:41 +0000
+++ src/CMakeLists.txt 2016-11-12 11:33:11 +0000
@@ -19,6 +19,10 @@
19 gstreamer-video-1.019 gstreamer-video-1.0
20 gstreamer-tag-1.020 gstreamer-tag-1.0
21 clutter-gst-3.021 clutter-gst-3.0
22 gupnp-1.0
23 gupnp-av-1.0
24 gssdp-1.0
25 libxml-2.0
22)26)
2327
24set (VALA_DEPS28set (VALA_DEPS
@@ -30,6 +34,10 @@
30 gstreamer-video-1.034 gstreamer-video-1.0
31 gstreamer-tag-1.035 gstreamer-tag-1.0
32 clutter-gst-3.036 clutter-gst-3.0
37 gupnp-1.0
38 gupnp-av-1.0
39 gssdp-1.0
40 libxml-2.0
33)41)
3442
35pkg_check_modules (DEPS REQUIRED ${PKG_DEPS})43pkg_check_modules (DEPS REQUIRED ${PKG_DEPS})
@@ -69,6 +77,7 @@
69 Services/Inhibitor.vala77 Services/Inhibitor.vala
70 Services/DirictoryMonitoring.vala78 Services/DirictoryMonitoring.vala
71 Services/LibraryManager.vala79 Services/LibraryManager.vala
80 Services/UPnPManager.vala
72 Services/Thubnailer.vala81 Services/Thubnailer.vala
7382
74 Objects/Video.vala83 Objects/Video.vala
7584
=== modified file 'src/Objects/Video.vala'
--- src/Objects/Video.vala 2016-09-27 20:26:37 +0000
+++ src/Objects/Video.vala 2016-11-12 11:33:11 +0000
@@ -1,4 +1,4 @@
1/*-1 /*-
2 * Copyright (c) 2016-2016 elementary LLC.2 * Copyright (c) 2016-2016 elementary LLC.
3 *3 *
4 * This program is free software: you can redistribute it and/or modify4 * This program is free software: you can redistribute it and/or modify
@@ -19,6 +19,9 @@
19 */19 */
2020
21namespace Audience.Objects {21namespace Audience.Objects {
22
23 public enum LibraryVideoType { LOCAL, UPNP }
24
22 public class Video : Object {25 public class Video : Object {
23 Audience.Services.LibraryManager manager;26 Audience.Services.LibraryManager manager;
2427
@@ -27,18 +30,12 @@
27 public signal void thumbnail_changed ();30 public signal void thumbnail_changed ();
28 public signal void trashed ();31 public signal void trashed ();
2932
30 public File video_file { get; private set; }33 public string title { get; construct set; }
31 public string directory { get; construct set; }
32 public string file { get; construct set; }
33
34 public string title { get; private set; }
35 public int year { get; private set; default = -1;}34 public int year { get; private set; default = -1;}
3635
37 public Gdk.Pixbuf? poster { get; private set; default = null; }36 public Gdk.Pixbuf? poster { get; private set; default = null; }
38 public Gdk.Pixbuf? thumbnail { get; private set; default = null; }
3937
40 public string mime_type { get; construct set; }38 public string mime_type { get; construct set; }
41 public string poster_cache_file { get; private set; }
4239
43 public string hash_file_poster { get; construct set; }40 public string hash_file_poster { get; construct set; }
44 public string hash_episode_poster { get; construct set; }41 public string hash_episode_poster { get; construct set; }
@@ -47,17 +44,33 @@
4744
48 public string container { get; construct set; default = ""; }45 public string container { get; construct set; default = ""; }
4946
50 public Video (string directory, string file, string mime_type) {47 // Local
51 Object (directory: directory, file: file, mime_type: mime_type);48 public File video_file { get; private set; }
52 }49 public string directory { get; construct set; }
5350 public string file { get; construct set; }
54 construct {51 public Gdk.Pixbuf? thumbnail { get; private set; default = null; }
55 manager = Audience.Services.LibraryManager.get_instance ();52 public string poster_cache_file { get; private set; }
56 manager.thumbler.finished.connect (dbus_finished);53
5754 // UPnP
58 title = Audience.get_title (file);55 public string video_uri { get; construct set; default = "";}
5956 public string poster_uri { get; construct set; }
60 extract_metadata ();57
58
59 public LibraryVideoType library_video_type { get; private set; }
60
61 public Video.local_media (string directory, string file, string mime_type) {
62 Object (title: Audience.get_title (file), directory: directory, file: file, mime_type: mime_type);
63 init_local ();
64 }
65
66 public Video.upnp_media (string container, string title, string video_uri, string poster_uri, string mime_type) {
67 Object (container: container, title: title, video_uri: video_uri, poster_uri: poster_uri, mime_type: mime_type);
68 init_upnp ();
69 }
70
71 private void init_local () {
72 library_video_type = LibraryVideoType.LOCAL;
73
61 video_file = File.new_for_path (this.get_path ());74 video_file = File.new_for_path (this.get_path ());
6275
63 if (directory != Audience.settings.library_folder) {76 if (directory != Audience.settings.library_folder) {
@@ -70,6 +83,20 @@
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");
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");
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");
86 }
87
88 private void init_upnp () {
89 library_video_type = LibraryVideoType.UPNP;
90 video_file = File.new_for_uri (video_uri);
91
92 hash_file_poster = GLib.Checksum.compute_for_string (ChecksumType.MD5, video_file.get_uri (), video_file.get_uri ().length);
93 thumbnail_large_path = Path.build_filename (GLib.Environment.get_user_cache_dir (),"thumbnails", "large", hash_file_poster + ".png");
94 thumbnail_normal_path = Path.build_filename (GLib.Environment.get_user_cache_dir (),"thumbnails", "normal", hash_file_poster + ".png");
95 }
96
97 construct {
98 manager = Audience.Services.LibraryManager.get_instance ();
99 manager.thumbler.finished.connect (dbus_finished);
73100
74 notify["poster"].connect (() => {101 notify["poster"].connect (() => {
75 poster_changed (this);102 poster_changed (this);
@@ -80,6 +107,8 @@
80 notify["thumbnail"].connect (() => {107 notify["thumbnail"].connect (() => {
81 thumbnail_changed ();108 thumbnail_changed ();
82 });109 });
110
111 extract_metadata ();
83 }112 }
84113
85 private void extract_metadata () {114 private void extract_metadata () {
@@ -87,8 +116,9 @@
87 MatchInfo info;116 MatchInfo info;
88 if (manager.regex_year.match (this.title, 0, out info)) {117 if (manager.regex_year.match (this.title, 0, out info)) {
89 year = int.parse (info.fetch (0).substring (1, 4));118 year = int.parse (info.fetch (0).substring (1, 4));
90 title = this.title.replace (info.fetch (0) + ")", "").strip ();119 title = title.replace (info.fetch (0) + ")", "").replace ("_", " ").strip ();
91 }120 }
121 title = title.replace ("_", " ").strip ();
92 }122 }
93123
94 public async void initialize_poster () {124 public async void initialize_poster () {
@@ -103,47 +133,73 @@
103 Gdk.Pixbuf? pixbuf = null;133 Gdk.Pixbuf? pixbuf = null;
104134
105 ThreadFunc<void*> run = () => {135 ThreadFunc<void*> run = () => {
106 if (!FileUtils.test (thumbnail_large_path, FileTest.EXISTS) || !FileUtils.test (thumbnail_normal_path, FileTest.EXISTS)) {136 if (library_video_type == LibraryVideoType.LOCAL) {
107 // Call DBUS for create a new THUMBNAIL137 if (!FileUtils.test (thumbnail_large_path, FileTest.EXISTS) || !FileUtils.test (thumbnail_normal_path, FileTest.EXISTS)) {
108 Gee.ArrayList<string> uris = new Gee.ArrayList<string> ();138 // Call DBUS for create a new THUMBNAIL
109 Gee.ArrayList<string> mimes = new Gee.ArrayList<string> ();139 Gee.ArrayList<string> uris = new Gee.ArrayList<string> ();
110140 Gee.ArrayList<string> mimes = new Gee.ArrayList<string> ();
111 uris.add (video_file.get_uri ());141 uris.add (video_file.get_uri ());
112 mimes.add (mime_type);142 mimes.add (mime_type);
113143
114 manager.thumbler.Instand (uris, mimes, "large");144 manager.thumbler.Instand (uris, mimes, "large");
115 manager.thumbler.Instand (uris, mimes, "normal");145 manager.thumbler.Instand (uris, mimes, "normal");
116 }146 }
117147
118 string? poster_path = poster_cache_file;148 string? poster_path = poster_cache_file;
119 pixbuf = manager.get_poster_from_file (poster_path);149 pixbuf = manager.get_poster_from_file (poster_path);
120150
121 // POSTER in Cache exists151 // POSTER in Cache exists
122 if (pixbuf != null) {152 if (pixbuf != null) {
123 Idle.add ((owned) callback);153 Idle.add ((owned) callback);
124 return null;154 return null;
125 }155 }
126156
127 poster_path = get_native_poster_path ();157 poster_path = get_native_poster_path ();
128 if (poster_path != null) {158 if (poster_path != null) {
129 pixbuf = manager.get_poster_from_file (poster_path);159 pixbuf = manager.get_poster_from_file (poster_path);
130 }160 }
131161
132 // POSTER found162 // POSTER found
133 if (pixbuf != null) {163 if (pixbuf != null) {
164 try {
165 pixbuf.save (poster_cache_file, "jpeg");
166 } catch (Error e) {
167 warning (e.message);
168 }
169 Idle.add ((owned) callback);
170 return null;
171 }
172
173 if (FileUtils.test (thumbnail_large_path, FileTest.EXISTS)) {
174 pixbuf = manager.get_poster_from_file (thumbnail_large_path);
175 Idle.add ((owned) callback);
176 return null;
177 }
178 } else if (library_video_type == LibraryVideoType.UPNP) {
179 string? poster_path = poster_uri;
180 pixbuf = manager.get_poster_from_file (poster_path);
181
182 // UPnP POSTER exists
183 if (pixbuf != null) {
184 Idle.add ((owned) callback);
185 return null;
186 }
187 var p = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, Audience.Services.POSTER_WIDTH, Audience.Services.POSTER_HEIGHT);
134 try {188 try {
135 pixbuf.save (poster_cache_file, "jpeg");189 pixbuf = Gtk.IconTheme.get_default().load_icon ("video-x-generic-symbolic", 64, Gtk.IconLookupFlags.USE_BUILTIN);
136 } catch (Error e) {190 } catch (Error e) {
137 warning (e.message);191 warning (e.message);
138 }192 }
139 Idle.add ((owned) callback);193 pixbuf.scale (p, 0, 0, Audience.Services.POSTER_WIDTH, Audience.Services.POSTER_HEIGHT,
140 return null;194 (Audience.Services.POSTER_WIDTH - 64) / 2,
141 }195 (Audience.Services.POSTER_HEIGHT - 64) / 2 - 1, 1, 1, Gdk.InterpType.HYPER);
142196
143 if (FileUtils.test (thumbnail_large_path, FileTest.EXISTS)) {197 pixbuf = p;
144 pixbuf = manager.get_poster_from_file (thumbnail_large_path);198
145 Idle.add ((owned) callback);199 if (pixbuf != null) {
146 return null;200 Idle.add ((owned) callback);
201 return null;
202 }
147 }203 }
148204
149 Idle.add ((owned) callback);205 Idle.add ((owned) callback);
150206
=== modified file 'src/Services/LibraryManager.vala'
--- src/Services/LibraryManager.vala 2016-09-27 20:26:37 +0000
+++ src/Services/LibraryManager.vala 2016-11-12 11:33:11 +0000
@@ -129,7 +129,7 @@
129 if (name == "") {129 if (name == "") {
130 name = file_info.get_name ();130 name = file_info.get_name ();
131 }131 }
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 ());
133 video_file_detected (video);133 video_file_detected (video);
134 poster_hash.add (video.hash_file_poster + ".jpg");134 poster_hash.add (video.hash_file_poster + ".jpg");
135 poster_hash.add (video.hash_episode_poster + ".jpg");135 poster_hash.add (video.hash_episode_poster + ".jpg");
@@ -194,22 +194,65 @@
194194
195 public Gdk.Pixbuf? get_poster_from_file (string poster_path) {195 public Gdk.Pixbuf? get_poster_from_file (string poster_path) {
196 Gdk.Pixbuf? pixbuf = null;196 Gdk.Pixbuf? pixbuf = null;
197 if (FileUtils.test (poster_path, FileTest.EXISTS)) {197
198 try {198 if (poster_path.has_prefix ("http")) {
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);
200 } catch (Error e) {200 FileInputStream http_input;
201 warning (e.message);201 try {
202 }202 http_input = http_file.read ();
203 if (pixbuf == null) {203 } catch (Error e) {
204 return null;204 warning ("Unable to fetch '%s': %s", poster_path, e.message);
205 }205 return null;
206 // Cut THUMBNAIL images206 }
207
208 var loader = new Gdk.PixbufLoader ();
209 var buf = new uint8[4096];
210 for (;;) {
211 ssize_t bytes_read;
212 try {
213 bytes_read = http_input.read (buf);
214 if (bytes_read == 0)
215 break;
216 } catch (IOError e) {
217 warning ("Unable to read data from '%s': %s", poster_path, e.message);
218 return null;
219 }
220 try {
221 loader.write (buf[0:bytes_read]);
222 } catch (Error e) {
223 warning ("Unable to parse image from '%s': %s", poster_path, e.message);
224 return null;
225 }
226 }
227 try {
228 loader.close ();
229 } catch (Error e) {
230 warning ("Unable to parse image from '%s': %s", poster_path, e.message);
231 return null;
232 }
233 pixbuf = loader.get_pixbuf ();
234 } else {
235 if (poster_path != "" && FileUtils.test (poster_path, FileTest.EXISTS)) {
236 try {
237 pixbuf = new Gdk.Pixbuf.from_file_at_scale (poster_path, -1, Audience.Services.POSTER_HEIGHT, true);
238 } catch (Error e) {
239 warning (e.message);
240 }
241 if (pixbuf == null) {
242 return null;
243 }
244 }
245 }
246
247 // Cut THUMBNAIL images
248 if (pixbuf != null) {
207 int width = pixbuf.width;249 int width = pixbuf.width;
208 if (width > Audience.Services.POSTER_WIDTH) {250 if (width > Audience.Services.POSTER_WIDTH) {
209 int x_offset = (width - Audience.Services.POSTER_WIDTH) / 2;251 int x_offset = (width - Audience.Services.POSTER_WIDTH) / 2;
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);
211 }253 }
212 }254 }
255
213 return pixbuf;256 return pixbuf;
214 }257 }
215 }258 }
216259
=== added file 'src/Services/UPnPManager.vala'
--- src/Services/UPnPManager.vala 1970-01-01 00:00:00 +0000
+++ src/Services/UPnPManager.vala 2016-11-12 11:33:11 +0000
@@ -0,0 +1,173 @@
1// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*-
2/*-
3 * Copyright (c) 2016-2016 elementary LLC.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * Authored by: Artem Anufrij <artem.anufrij@live.de>
19 *
20 */
21
22namespace Audience.Services {
23 public class UPnPManager : Object {
24
25 public signal void video_item_detected (Audience.Objects.Video video);
26 public signal void sevice_unavailable (string device_uri);
27
28 GUPnP.Context context;
29 GUPnP.ControlPoint point;
30
31 public static UPnPManager instance = null;
32 public static UPnPManager get_instance () {
33 if (instance == null) {
34 instance = new UPnPManager ();
35 }
36
37 return instance;
38 }
39
40 private UPnPManager () {}
41
42 construct {
43 context = new GUPnP.Context(null, null, 0);
44
45 point = new GUPnP.ControlPoint(context, "urn:schemas-upnp-org:device:MediaServer:1");
46 point.device_proxy_unavailable.connect ((device_proxy) => {
47 debug ("DEVICE OFF: %s", device_proxy.get_friendly_name ());
48 string device_uri = device_proxy.get_presentation_url ();
49 sevice_unavailable (device_uri);
50 });
51 point.device_proxy_available.connect ((device_proxy) => {
52 debug ("DEVICE ON: %s", device_proxy.get_friendly_name ());
53 foreach (var si in device_proxy.list_services ()) {
54 if (si.service_type.has_prefix("urn:schemas-upnp-org:service:ContentDirectory") && si is GUPnP.ServiceProxy) {
55 var proxy = si as GUPnP.ServiceProxy;
56 try {
57 Gee.ArrayList<string> items = new Gee.ArrayList<string> ();
58 expand_content (proxy, "0", items, "root");
59 } catch (Error e) {
60 warning (e.message);
61 }
62 }
63 }
64 debug ("DEVICE SCAN FINISHED: %s", device_proxy.get_friendly_name ());
65 });
66 }
67
68 private void expand_content (GUPnP.ServiceProxy proxy, string object_id, Gee.ArrayList<string> items, string parent) {
69 // SEARCH PARAMETER IN
70 List<string> in_names = new List<string> ();
71 List<unowned Value?> in_values = new List<unowned Value?> ();
72 in_names.append ("ObjectID");
73 in_names.append ("BrowseFlag");
74 in_names.append ("StartingIndex");
75 in_names.append ("RequestedCount");
76 in_names.append ("Filter");
77 in_names.append ("SortCriteria");
78 var val_object_id = Value (typeof (string));
79 val_object_id.set_string (object_id);
80 var val_browse_crit = Value (typeof (string));
81 val_browse_crit.set_string ("BrowseDirectChildren");
82 var val_start_index = Value (typeof (int));
83 val_start_index.set_int (0);
84 var val_req_count = Value (typeof (int));
85 val_req_count.set_int (0);
86 var val_filter = Value (typeof (string));
87 val_filter.set_string ("*");
88 var val_sort_crit = Value (typeof (string));
89 val_sort_crit.set_string ("");
90 in_values.append (val_object_id);
91 in_values.append (val_browse_crit);
92 in_values.append (val_start_index);
93 in_values.append (val_req_count);
94 in_values.append (val_filter);
95 in_values.append (val_sort_crit);
96
97 // SEARCH PARAMETER OUT
98 List<string> out_names = new List<string> ();
99 List<Type> out_types = new List<Type> ();
100 List<Value*> out_values = new List<Value*> ();
101 out_names.append ("Result");
102 out_types.append (typeof (string));
103
104 proxy.send_action_list ("Browse", in_names, in_values, out_names, out_types, out out_values);
105
106 var parser = new GUPnP.DIDLLiteParser ();
107 parser.container_available.connect ((container) => {
108 if (container.id != object_id && (container.parent_id == object_id || object_id == "0")) {
109 expand_content (proxy, container.id, items, container.title);
110 }
111 });
112 parser.item_available.connect ((item) => {
113 crate_upnp_object (item, items, parent);
114 });
115
116 // PARSE ITEMS
117 foreach (Value* v in out_values) {
118 string output = v.get_string();
119 v.unset();
120 GLib.Slice.free (sizeof(Value), v);
121 try {
122 GUPnP.MediaCollection media_collection = new GUPnP.MediaCollection.from_string (output);
123 if (media_collection.get_items ().length () > 0) {
124 foreach (var item in media_collection.get_items ()) {
125 crate_upnp_object (item, items, parent);
126 }
127 } else {
128 parser.parse_didl (output);
129 }
130
131 } catch (Error e) {
132 //PARSTER THROWS AN EXCEPTION IF VALUE IS EMPTY
133 //IT CAN BE IGNORED
134 debug (e.message);
135 }
136 }
137 }
138
139 private void crate_upnp_object (GUPnP.DIDLLiteObject item, Gee.ArrayList<string> items, string parent) {
140 if (item.upnp_class.has_prefix("object.item.videoItem")) {
141 string video_uri = "";
142 string poster_uri = "";
143 string mime_type = "";
144
145 foreach (var res in item.get_resources ()) {
146 if (res.protocol_info.mime_type.has_prefix ("video")) {
147 video_uri = res.uri;
148 mime_type = res.protocol_info.mime_type;
149 } else if (res.protocol_info.mime_type.has_prefix ("image")) {
150 poster_uri = res.uri;
151 }
152 }
153
154 if (poster_uri == "" && item.album_art != null) {
155 poster_uri = item.album_art;
156 }
157
158 if (video_uri != "" && !items.contains (video_uri)) {
159 items.add (video_uri);
160 video_item_detected (new Audience.Objects.Video.upnp_media (parent, item.title, video_uri, poster_uri, mime_type));
161 }
162 }
163 }
164
165 public bool active {
166 get {
167 return point.active;
168 } set {
169 point.active = value;
170 }
171 }
172 }
173}
0174
=== modified file 'src/Widgets/Library/EpisodesPage.vala'
--- src/Widgets/Library/EpisodesPage.vala 2016-11-02 23:59:41 +0000
+++ src/Widgets/Library/EpisodesPage.vala 2016-11-12 11:33:11 +0000
@@ -27,6 +27,7 @@
27 Granite.Widgets.AlertView alert_view;27 Granite.Widgets.AlertView alert_view;
2828
29 public Audience.Services.LibraryManager manager;29 public Audience.Services.LibraryManager manager;
30 public Audience.Services.UPnPManager upnp;
3031
31 string query;32 string query;
3233
@@ -65,6 +66,9 @@
65 manager = Audience.Services.LibraryManager.get_instance ();66 manager = Audience.Services.LibraryManager.get_instance ();
66 manager.video_file_deleted.connect (remove_item_from_path);67 manager.video_file_deleted.connect (remove_item_from_path);
67 manager.video_file_detected.connect (add_item);68 manager.video_file_detected.connect (add_item);
69
70 upnp = Audience.Services.UPnPManager.get_instance ();
71 upnp.sevice_unavailable.connect (remove_item_from_path);
68 }72 }
6973
70 public void set_episodes_items (Gee.ArrayList<Audience.Objects.Video> episodes) {74 public void set_episodes_items (Gee.ArrayList<Audience.Objects.Video> episodes) {
@@ -80,6 +84,7 @@
80 private void play_video (Gtk.FlowBoxChild item) {84 private void play_video (Gtk.FlowBoxChild item) {
81 var selected = (item as Audience.LibraryItem);85 var selected = (item as Audience.LibraryItem);
82 var video = selected.episodes.first ();86 var video = selected.episodes.first ();
87 debug (video.video_file.get_uri () + " - " + video.video_file.query_exists ().to_string ());
83 if (video.video_file.query_exists ()) {88 if (video.video_file.query_exists ()) {
84 bool from_beginning = video.video_file.get_uri () != settings.current_video;89 bool from_beginning = video.video_file.get_uri () != settings.current_video;
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);
@@ -102,10 +107,9 @@
102 }107 }
103108
104 string[] filter_elements = query.split (" ");109 string[] filter_elements = query.split (" ");
105 var video_title = (child as LibraryItem).get_title ();
106110
107 foreach (string filter_element in filter_elements) {111 foreach (string filter_element in filter_elements) {
108 if (!video_title.down ().contains (filter_element.down ())) {112 if (!(child as LibraryItem).title.down ().contains (filter_element.down ())) {
109 return false;113 return false;
110 }114 }
111 }115 }
@@ -116,7 +120,7 @@
116 var item1 = (LibraryItem)child1;120 var item1 = (LibraryItem)child1;
117 var item2 = (LibraryItem)child2;121 var item2 = (LibraryItem)child2;
118 if (item1 != null && item2 != null) {122 if (item1 != null && item2 != null) {
119 return item1.episodes.first ().file.collate (item2.episodes.first ().file);123 return item1.episodes.first ().title.collate (item2.episodes.first ().title);
120 }124 }
121 return 0;125 return 0;
122 }126 }
@@ -132,7 +136,9 @@
132136
133 private async void remove_item_from_path (string path ) {137 private async void remove_item_from_path (string path ) {
134 foreach (var child in view_episodes.get_children ()) {138 foreach (var child in view_episodes.get_children ()) {
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
140 || (child as LibraryItem).episodes.first ().video_file.get_uri ().has_prefix (path)
141 || (child as LibraryItem).episodes.first ().video_file.get_path ().has_prefix (path)) {
136 child.dispose ();142 child.dispose ();
137 }143 }
138 }144 }
139145
=== modified file 'src/Widgets/Library/LibraryItem.vala'
--- src/Widgets/Library/LibraryItem.vala 2016-11-02 23:59:41 +0000
+++ src/Widgets/Library/LibraryItem.vala 2016-11-12 11:33:11 +0000
@@ -50,9 +50,13 @@
50 video.title_changed.connect (video_title_changed);50 video.title_changed.connect (video_title_changed);
51 video.poster_changed.connect (video_poster_changed);51 video.poster_changed.connect (video_poster_changed);
5252
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) {
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);
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");
56 poster_cache_file = Path.build_filename (App.get_instance ().get_cache_directory (), hash + ".jpg");
57 } else if (video.library_video_type == Audience.Objects.LibraryVideoType.UPNP) {
58 poster_cache_file = video.poster_uri;
59 }
56 }60 }
5761
58 construct {62 construct {
@@ -141,7 +145,9 @@
141 }145 }
142146
143 private bool show_context_menu (Gtk.Widget sender, Gdk.EventButton evt) {147 private bool show_context_menu (Gtk.Widget sender, Gdk.EventButton evt) {
144 if (evt.type == Gdk.EventType.BUTTON_PRESS && evt.button == 3) {148 if (episodes.first ().library_video_type == Audience.Objects.LibraryVideoType.LOCAL
149 && evt.type == Gdk.EventType.BUTTON_PRESS
150 && evt.button == 3) {
145 context_menu.popup (null, null, null, evt.button, evt.time);151 context_menu.popup (null, null, null, evt.button, evt.time);
146 return true;152 return true;
147 }153 }
@@ -156,9 +162,9 @@
156162
157 private void video_title_changed (Audience.Objects.Video video) {163 private void video_title_changed (Audience.Objects.Video video) {
158 if (episodes.size == 1) {164 if (episodes.size == 1) {
159 title_label.label = video.title;165 title = video.title;
160 } else {166 } else {
161 title_label.label = video.container;167 title = video.container;
162 }168 }
163 title_label.show ();169 title_label.show ();
164 }170 }
@@ -220,15 +226,19 @@
220 });226 });
221 episodes.add (episode);227 episodes.add (episode);
222 if (episodes.size == 1) {228 if (episodes.size == 1) {
223 title_label.label = episode.title;229 title = episode.title;
224 } else if (episodes.size == 2) {230 } else if (episodes.size == 2) {
225 title_label.label = episode.container;231 title = episode.container;
226 create_episode_poster ();232 create_episode_poster ();
227 }233 }
228 }234 }
229235
230 public string get_title () {236 public string title {
231 return title_label.label;237 get {
238 return title_label.label;
239 } set {
240 title_label.label = value;
241 }
232 }242 }
233243
234 public void create_episode_poster () {244 public void create_episode_poster () {
235245
=== modified file 'src/Widgets/Library/LibraryPage.vala'
--- src/Widgets/Library/LibraryPage.vala 2016-11-02 23:59:41 +0000
+++ src/Widgets/Library/LibraryPage.vala 2016-11-12 11:33:11 +0000
@@ -27,6 +27,7 @@
2727
28 public Gtk.FlowBox view_movies;28 public Gtk.FlowBox view_movies;
29 public Audience.Services.LibraryManager manager;29 public Audience.Services.LibraryManager manager;
30 public Audience.Services.UPnPManager upnp;
30 public Gtk.ScrolledWindow scrolled_window;31 public Gtk.ScrolledWindow scrolled_window;
31 bool poster_initialized = false;32 bool poster_initialized = false;
32 string query;33 string query;
@@ -45,7 +46,7 @@
4546
46 construct {47 construct {
47 manager = Audience.Services.LibraryManager.get_instance ();48 manager = Audience.Services.LibraryManager.get_instance ();
4849 upnp = Audience.Services.UPnPManager.get_instance ();
49 query = "";50 query = "";
5051
51 scrolled_window = new Gtk.ScrolledWindow (null, null);52 scrolled_window = new Gtk.ScrolledWindow (null, null);
@@ -62,7 +63,16 @@
6263
63 scrolled_window.add (view_movies);64 scrolled_window.add (view_movies);
6465
65 manager = Audience.Services.LibraryManager.get_instance ();66 upnp.sevice_unavailable.connect ((device_uri) => {
67 foreach (var child in view_movies.get_children ()) {
68 foreach (var video in (child as LibraryItem).episodes) {
69 if (video.video_uri != null && video.video_uri.has_prefix(device_uri)) {
70 remove_item.begin (child as LibraryItem);
71 }
72 }
73 }
74 });
75
66 manager.video_file_detected.connect (add_item);76 manager.video_file_detected.connect (add_item);
67 manager.video_file_deleted.connect (remove_item_from_path);77 manager.video_file_deleted.connect (remove_item_from_path);
68 manager.video_moved_to_trash.connect ((video) => {78 manager.video_moved_to_trash.connect ((video) => {
@@ -71,6 +81,10 @@
7181
72 manager.begin_scan ();82 manager.begin_scan ();
7383
84 upnp.video_item_detected.connect (add_item);
85 upnp.active = true;
86
87
74 map.connect (() => {88 map.connect (() => {
75 if (!poster_initialized) {89 if (!poster_initialized) {
76 poster_initialized = true;90 poster_initialized = true;
@@ -89,7 +103,11 @@
89103
90 if (selected.episodes.size == 1) {104 if (selected.episodes.size == 1) {
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;
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) {
107 App.get_instance ().mainwindow.play_file (selected.episodes.first ().video_file.get_uri (), from_beginning);
108 } else if (selected.episodes.first ().library_video_type == Audience.Objects.LibraryVideoType.UPNP) {
109 App.get_instance ().mainwindow.play_file (selected.episodes.first ().video_uri, from_beginning);
110 }
93 } else {111 } else {
94 last_filter = query;112 last_filter = query;
95 show_episodes (selected);113 show_episodes (selected);
@@ -100,6 +118,9 @@
100 foreach (var child in view_movies.get_children ()) {118 foreach (var child in view_movies.get_children ()) {
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) {
102 (child as LibraryItem).add_episode (video);120 (child as LibraryItem).add_episode (video);
121 if ((child as LibraryItem).episodes.size == 2) {
122 view_movies.invalidate_sort ();
123 }
103 return;124 return;
104 }125 }
105 }126 }
@@ -113,7 +134,9 @@
113134
114 private async void remove_item (LibraryItem item) {135 private async void remove_item (LibraryItem item) {
115 foreach (var video in item.episodes) {136 foreach (var video in item.episodes) {
116 manager.clear_cache.begin (video.poster_cache_file);137 if (video.poster_cache_file != null) {
138 manager.clear_cache.begin (video.poster_cache_file);
139 }
117 }140 }
118 item.dispose ();141 item.dispose ();
119 }142 }
@@ -142,10 +165,9 @@
142 }165 }
143166
144 string[] filter_elements = query.split (" ");167 string[] filter_elements = query.split (" ");
145 var video_title = (child as LibraryItem).get_title ();
146168
147 foreach (string filter_element in filter_elements) {169 foreach (string filter_element in filter_elements) {
148 if (!video_title.down ().contains (filter_element.down ())) {170 if (!(child as LibraryItem).title.down ().contains (filter_element.down ())) {
149 return false;171 return false;
150 }172 }
151 }173 }
@@ -156,7 +178,7 @@
156 var item1 = (LibraryItem)child1;178 var item1 = (LibraryItem)child1;
157 var item2 = (LibraryItem)child2;179 var item2 = (LibraryItem)child2;
158 if (item1 != null && item2 != null) {180 if (item1 != null && item2 != null) {
159 return item1.get_title ().collate (item2.get_title ());181 return item1.title.collate (item2.title);
160 }182 }
161 return 0;183 return 0;
162 }184 }
163185
=== modified file 'src/Widgets/WelcomePage.vala'
--- src/Widgets/WelcomePage.vala 2016-09-28 02:32:33 +0000
+++ src/Widgets/WelcomePage.vala 2016-11-12 11:33:11 +0000
@@ -2,6 +2,7 @@
2 public class WelcomePage : Granite.Widgets.Welcome {2 public class WelcomePage : Granite.Widgets.Welcome {
3 private DiskManager disk_manager;3 private DiskManager disk_manager;
4 private Services.LibraryManager library_manager;4 private Services.LibraryManager library_manager;
5 private Services.UPnPManager upnp_manager;
5 public WelcomePage () {6 public WelcomePage () {
6 base (_("No Videos Open"), _("Select a source to begin playing."));7 base (_("No Videos Open"), _("Select a source to begin playing."));
7 }8 }
@@ -44,6 +45,12 @@
44 set_item_visible (3, LibraryPage.get_instance ().has_items);45 set_item_visible (3, LibraryPage.get_instance ().has_items);
45 });46 });
4647
48 upnp_manager = Services.UPnPManager.get_instance ();
49 upnp_manager.video_item_detected.connect ((vid) => {
50 set_item_visible (3, true);
51 this.show_all ();
52 });
53
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"));
48 set_item_visible (2, disk_manager.has_media_volumes ());55 set_item_visible (2, disk_manager.has_media_volumes ());
4956
5057
=== modified file 'src/Window.vala'
--- src/Window.vala 2016-09-27 19:58:34 +0000
+++ src/Window.vala 2016-11-12 11:33:11 +0000
@@ -106,7 +106,7 @@
106 episodes_page.set_episodes_items (item.episodes);106 episodes_page.set_episodes_items (item.episodes);
107 episodes_page.poster.pixbuf = item.poster.pixbuf;107 episodes_page.poster.pixbuf = item.poster.pixbuf;
108 main_stack.set_visible_child (episodes_page);108 main_stack.set_visible_child (episodes_page);
109 this.title = item.get_title ();109 this.title = item.title;
110 search_entry.text = "";110 search_entry.text = "";
111 });111 });
112112

Subscribers

People subscribed via source and target branches