diff --git a/meson.build b/meson.build index 5f862f7..ac4a804 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project ( 'com.github.louis77.tuner', 'vala', 'c', - version: '2.0.0-BETA2', + version: '2.0.0', meson_version: '>= 0.60.0', ) diff --git a/src/Application.vala b/src/Application.vala index d978055..7f0ec85 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -168,6 +168,9 @@ namespace Tuner { public class Application : Gtk.Application { + /** @brief Signal emitted when the shuffle mode changes */ + public signal void shuffle_mode_sig(bool shuffle); + private static Gtk.Settings GTK_SETTINGS; private static string GTK_SYSTEM_THEME = "unset"; private static string ENV_LANG = "LANGUAGE"; diff --git a/src/Controllers/PlayerController.vala b/src/Controllers/PlayerController.vala index 0b92645..e19c1c4 100644 --- a/src/Controllers/PlayerController.vala +++ b/src/Controllers/PlayerController.vala @@ -40,7 +40,7 @@ public class Tuner.PlayerController : GLib.Object public signal void state_changed_sig (Model.Station station, Is state); // /** Signal emitted when the title changes. */ - public signal void metadata_changed_sig (Model.Station station, Metadata metadata); + public signal void metadata_changed_sig (Model.Station station, Model.Metadata metadata); /** Signal emitted when the volume changes. */ public signal void volume_changed_sig (double volume); @@ -48,9 +48,6 @@ public class Tuner.PlayerController : GLib.Object /** Signal emitted every ten minutes that a station has been playing continuously. */ public signal void tape_counter_sig (Model.Station station); - /** @brief Signal emitted when the shuffle mode changes */ - public signal void shuffle_mode_sig(bool shuffle); - /** @brief Signal emitted when the shuffle is requested */ public signal void shuffle_requested_sig(); @@ -61,7 +58,7 @@ public class Tuner.PlayerController : GLib.Object private Player _player; private Model.Station _station; - private Metadata _metadata; + private Model.Metadata _metadata; private Is _player_state; private string _player_state_name; private uint _tape_counter_id = 0; @@ -103,7 +100,6 @@ public class Tuner.PlayerController : GLib.Object set_play_state (state.get_name ()); } }); - } // construct @@ -181,7 +177,7 @@ public class Tuner.PlayerController : GLib.Object }); } } // set - } // player_state + } // player_state /** @@ -195,7 +191,7 @@ public class Tuner.PlayerController : GLib.Object set { if ( ( _station == null ) || ( _station != value ) ) { - _metadata = new Metadata(); + _metadata = new Model.Metadata(); _station = value; play_station (_station); } @@ -213,11 +209,11 @@ public class Tuner.PlayerController : GLib.Object } -/** - * @brief Plays the specified station. - * - * @param station The station to play. - */ + /** + * @brief Plays the specified station. + * + * @param station The station to play. + */ public void play_station (Model.Station station) { _player.stop (); @@ -269,188 +265,13 @@ public class Tuner.PlayerController : GLib.Object } // stop + /** + * Shuffles the current playlist. + * + * This method randomizes the order of the tracks in the current playlist. + */ public void shuffle () { shuffle_requested_sig(); - } // shuffle - -/** - * @class Metadata - * - * @brief Stream Metadata transform - * - */ - public class Metadata : GLib.Object - { - private static string[,] METADATA_TITLES = - // Ordered array of tags and descriptions - { - {"title", _("Title") }, - {"artist", _("Artist") }, - {"album", _("Album") }, - {"image", _("Image") }, - {"genre", _("Genre") }, - {"homepage", _("Homepage") }, - {"organization", _("Organization") }, - {"location", _("Location") }, - {"extended-comment", _("Extended Comment") }, - {"bitrate", _("Bitrate") }, - {"audio-codec", _("Audio Codec") }, - {"channel-mode", _("Channel Mode") }, - {"track-number", _("Track Number") }, - {"track-count", _("Track Count") }, - {"nominal-bitrate", _("Nominal Bitrate") }, - {"minimum-bitrate", _("Minimum Bitrate") }, - {"maximum-bitrate", _("Maximim Bitrate") }, - {"container-format", ("Container Format") }, - {"application-name", _("Application Name") }, - {"encoder", _("Encoder") }, - {"encoder-version", _("Encoder Version") }, - {"datetime", _("Date Time") }, - {"private-data", _("Private Data") }, - {"has-crc", _("Has CRC") } - }; - - private static Gee.List METADATA_TAGS = new Gee.ArrayList (); - - static construct { - - uint8 tag_index = 0; - foreach ( var tag in METADATA_TITLES ) - // Replicating the order in METADATA_TITLES - { - if ((tag_index++)%2 == 0) - METADATA_TAGS.insert (tag_index/2, tag ); - } - } - - public string all_tags { get; private set; default = ""; } - public string title { get; private set; default = ""; } - public string artist { get; private set; default = ""; } - public string image { get; private set; default = ""; } - public string genre { get; private set; default = ""; } - public string homepage { get; private set; default = ""; } - public string audio_info { get; private set; default = ""; } - public string org_loc { get; private set; default = ""; } - public string pretty_print { get; private set; default = ""; } - - private Gee.Map _metadata_values = new Gee.HashMap(); // Hope it come out in order - - - /** - * Extracts the metadata from the media stream. - * - * @param media_info The media information stream - * @return true if the metadata has changed - */ - internal bool process_media_info_update (PlayerMediaInfo media_info) - { - var streamlist = media_info.get_stream_list ().copy (); - - title = ""; - artist = ""; - image = ""; - genre = ""; - homepage = ""; - audio_info = ""; - org_loc = ""; - pretty_print = ""; - - foreach (var stream in streamlist) // Hopefully just one metadata stream - { - var? tags = stream.get_tags (); // Get the raw tags - - if (tags == null) - break; // No tags, break on this metadata stream - - if (all_tags == tags.to_string ()) - return false; // Compare to all tags and if no change return false - - all_tags = tags.to_string (); - debug(@"All Tags: $all_tags"); - - string? s = null; - bool b = false; - uint u = 0; - - tags.foreach ((list, tag) => - { - var index = METADATA_TAGS.index_of (tag); - - if (index == -1) - { - warning(@"New meta tag: $tag"); - return; - } - - var type = (list.get_value_index(tag, 0)).type(); - - switch (type) - { - case GLib.Type.STRING: - list.get_string(tag, out s); - _metadata_values.set ( tag, s); - break; - case GLib.Type.UINT: - list.get_uint(tag, out u); - if ( u > 1000) - _metadata_values.set ( tag, @"$(u/1000)K"); - else - _metadata_values.set ( tag, u.to_string ()); - break; - case GLib.Type.BOOLEAN: - list.get_boolean (tag, out b); - _metadata_values.set ( tag, b.to_string ()); - break; - default: - warning(@"New Tag type: $(type.name())"); - break; - } - }); // tags.foreach - - if (_metadata_values.has_key ("title" )) - _title = _metadata_values.get ("title"); - if (_metadata_values.has_key ("artist" )) - _artist = _metadata_values.get ("artist"); - if (_metadata_values.has_key ("image" )) - _image = _metadata_values.get ("image"); - if (_metadata_values.has_key ("genre" )) - _genre = _metadata_values.get ("genre"); - if (_metadata_values.has_key ("homepage" )) - _homepage = _metadata_values.get ("homepage"); - - if (_metadata_values.has_key ("audio_codec" )) - _audio_info = _metadata_values.get ("audio_codec "); - if (_metadata_values.has_key ("bitrate" )) - _audio_info += _metadata_values.get ("bitrate "); - if (_metadata_values.has_key ("channel_mode" )) - _audio_info += _metadata_values.get ("channel_mode"); - if (_audio_info != null && _audio_info.length > 0) - _audio_info = safestrip(_audio_info); - - if (_metadata_values.has_key ("organization" )) - _org_loc = _metadata_values.get ("organization "); - if (_metadata_values.has_key ("location" )) - _org_loc += _metadata_values.get ("location"); - if (_org_loc != null && _org_loc.length > 0) - org_loc = safestrip(_org_loc); - - StringBuilder sb = new StringBuilder (); - foreach ( var tag in METADATA_TAGS ) - // Pretty print - { - if (_metadata_values.has_key(tag)) - { - sb.append ( METADATA_TITLES[METADATA_TAGS.index_of (tag),1]) - .append(" : ") - .append( _metadata_values.get (tag)) - .append("\n"); - } - } - pretty_print = sb.truncate (sb.len-1).str; - } // foreach - - return true; - } // process_media_info_update - } // Metadata + } // shuffle } // PlayerController diff --git a/src/Controllers/SearchController.vala b/src/Controllers/SearchController.vala index 5a2eafc..05b184a 100644 --- a/src/Controllers/SearchController.vala +++ b/src/Controllers/SearchController.vala @@ -28,7 +28,7 @@ using Gee; */ public class Tuner.SearchController : Object { - private const uint SEARCH_DELAY = 250; + private const uint SEARCH_DELAY = 333; public signal void search_for_sig(string text); private DirectoryController _directory; diff --git a/src/Models/Favicon.vala b/src/Models/Favicon.vala new file mode 100644 index 0000000..1bdbfcd --- /dev/null +++ b/src/Models/Favicon.vala @@ -0,0 +1,179 @@ +/** + * SPDX-FileCopyrightText: Copyright © 2020-2024 Louis Brauer + * SPDX-FileCopyrightText: Copyright © 2024 technosf + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * @file Station.vala + * + * @brief Station metadata and related cachable objects + * + */ + +using Gee; + +/** + * @class Station + * @brief Represents a radio station with various properties. + */ +public abstract class Tuner.Model.Favicon : Object +{ + public signal void favicon_sig(); // Station favicon loaded + + // ---------------------------------------------------------- + // statics + // ---------------------------------------------------------- + + // Stations with Favicons that failed to load + private static Set FAILING_FAVICON = new HashSet(); + + private const int FADE_MS = 400; + + + // ---------------------------------------------------------- + // Properties + // ---------------------------------------------------------- + + + /** @property {string} favicon - Favicon URL of the station. */ + private string _favicon; + + public string favicon { + get { return _favicon; } + protected set { + if (_favicon == value || favicon == null || favicon.length == 0 ) return; + _favicon = value; + try + { + _favicon_uri = Uri.parse(value, NONE); + } catch (GLib.UriError e) + { + FAILING_FAVICON.add(value); + } + } + } // favicon + + + // ---------------------------------------------------------- + // Privates + // ---------------------------------------------------------- + + private Uri _favicon_uri; + private int _favicon_loaded = 0; // Indicates the number of times the favicon has been loaded from cache or internet + private Gdk.Pixbuf _favicon_pixbuf; // Favicon for this station + + + protected abstract string favicon_cache_file(); + + public int favicon_loaded() + { + return _favicon_loaded; + } + // ---------------------------------------------------------- + // Methods + // ---------------------------------------------------------- + + + /** + * @brief Asynchronously loads the favicon for the station. + * + * Loads from cache is not requesting reload and cache exists + * Otherwise async calls to the website to download the favicon, + * then stores it in the cache and replaces the Station favicon + * + * @param {bool} reload - Whether to force reload the favicon. + */ + protected async void load_favicon_async( bool reload = false ) + { + /* + Get favicon from cache file if file is in cache AND + Not requesting reload Or favicon is currently failing + */ + if ( ( !reload || FAILING_FAVICON.contains(_favicon) ) + && FileUtils.test(favicon_cache_file(), FileTest.EXISTS)) + { + try { + var pixbuf = new Gdk.Pixbuf.from_file_at_scale(favicon_cache_file(), 48, 48, true); + _favicon_pixbuf = pixbuf; + _favicon_loaded++; + favicon_sig(); + return; + } catch (Error e) { + info(@"$(_favicon) - Failed to load cached favicon: $(e.message)"); + } + } + + if (_favicon_uri == null) + return; // First load or reload requested and favicon is not failing + + uint status_code; + + InputStream? stream = yield HttpClient.GETasync(_favicon_uri, Priority.LOW, out status_code); // Will automatically try several times + + if ( stream != null && status_code == 200 ) + /* + Input stream OK + */ + { + try { + var pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(stream, 48, 48, true,null); + pixbuf.save(favicon_cache_file(), "png"); + _favicon_pixbuf = pixbuf; + _favicon_loaded++; + favicon_sig(); + return; + + } catch (Error e) { + debug(@"$(_favicon) - Failed to process favicon $(_favicon_uri.to_string()) - $(e.message)"); + } + } + info(@"$(_favicon) - Failed to load favicon $(_favicon_uri.to_string()) - Status code: $(status_code)"); + FAILING_FAVICON.add(_favicon); + } // load_favicon_async + + + /** + * @brief Asynchronously sets the given image to the station favicon, if available. + * + * Load the known icons first, and if reload, go around after trying the favicon url + * + * @param {Image} favicon_image - The favicon image to be updated. + * @param {bool} reload - Whether to force reload the favicon from source. + * @return {bool} True if the favicon was available and the image updated. + */ + public async bool update_favicon_image( Gtk.Image favicon_image, bool reload = false, string defaulticon = "") + { + bool reloading = false; + do { + try{ + if ( _favicon_pixbuf == null || FAILING_FAVICON.contains(_favicon)) + { + yield fade(favicon_image, FADE_MS, false); + favicon_image.set_from_icon_name(defaulticon,Gtk.IconSize.DIALOG); + yield fade(favicon_image, FADE_MS, true); + } + else{ + + yield fade(favicon_image, FADE_MS, false); + favicon_image.set_from_pixbuf(_favicon_pixbuf); + yield fade(favicon_image, FADE_MS, true); + } + } finally { + favicon_image.opacity = 1; + reloading = false; + } + + if ( reload && _favicon_loaded < 2 ) + /* + Reload requested, and favicon has not had reload requested before + */ + { + FAILING_FAVICON.remove(_favicon); // Give possible 2nd chance + yield load_favicon_async(true); // Wait for load_favicon_async to complete + reloading = true; + reload = false; + } + } while ( reloading ); + return true; + } // update_favicon_image +} // Station diff --git a/src/Models/Station.vala b/src/Models/Station.vala index aadafa3..28dee9e 100644 --- a/src/Models/Station.vala +++ b/src/Models/Station.vala @@ -16,7 +16,7 @@ using Gee; * @class Station * @brief Represents a radio station with various properties. */ -public class Tuner.Model.Station : Object +public class Tuner.Model.Station : Favicon { // ---------------------------------------------------------- @@ -24,17 +24,15 @@ public class Tuner.Model.Station : Object // ---------------------------------------------------------- // Stations with Favicons that failed to load - private static Set STATION_FAILING_FAVICON = new HashSet(); + // private static Set STATION_FAILING_FAVICON = new HashSet(); // Core set of all station so far retrieved private static Map STATIONS = new HashMap(); - private const int FADE_MS = 400; // Signals public signal void station_star_changed_sig( bool starred ); // Station starred state has changed - public signal void station_favicon_sig(); // Station favicon loaded // ---------------------------------------------------------- // Properties @@ -53,7 +51,7 @@ public class Tuner.Model.Station : Object /** @property {string} homepage - Homepage of the station. */ public string homepage { get; private set; } /** @property {string} favicon - Favicon URL of the station. */ - public string favicon { get; private set ; } + // public string favicon { get; private set ; } /** @property {string} tags - Tags associated with the station. */ public string tags { get; private set ; } /** @property {string} country - Country where the station is located. */ @@ -129,7 +127,7 @@ public class Tuner.Model.Station : Object } - public int favicon_loaded; // Indicates the number of times the favicon has been loaded from cache or internet + // public int favicon_loaded; // Indicates the number of times the favicon has been loaded from cache or internet public bool is_in_index; // Indicates if the station is in the provider index public bool is_up_to_date; // Indicates if the station is up-to-date with the provider index public string up_to_date_difference = _("Station no longer in the index"); @@ -139,8 +137,8 @@ public class Tuner.Model.Station : Object // Privates // ---------------------------------------------------------- - private Uri _favicon_uri; - private Gdk.Pixbuf _favicon_pixbuf; // Favicon for this station + // private Uri _favicon_uri; + // private Gdk.Pixbuf _favicon_pixbuf; // Favicon for this station private string _favicon_cache_file; @@ -299,28 +297,9 @@ public class Tuner.Model.Station : Object is_up_to_date = false; // Basic station creation - assume not up-to-date with provider /* - Favicon setup + Favicon load */ - favicon_loaded = 0;// Used to notify that favicon loaded - _favicon_cache_file = Path.build_filename(Application.instance.cache_dir, stationuuid); - - if (favicon == null || favicon.length == 0) - { - STATION_FAILING_FAVICON.add(stationuuid); - debug(@"$(stationuuid) - Favicon missing"); - return; - } - - try - { - debug(@"$(stationuuid) - constructed - Start parse favicon URL: $(favicon)"); - _favicon_uri = Uri.parse(favicon, NONE); - } catch (GLib.UriError e) - { - info(@"$(stationuuid) - Failed to parse favicon URL: $(e.message)"); - STATION_FAILING_FAVICON.add(stationuuid); - } - + _favicon_cache_file = Path.build_filename(Application.instance.cache_dir, stationuuid); load_favicon_async.begin(); } // Station.basic @@ -354,114 +333,6 @@ public class Tuner.Model.Station : Object } // to_string - /** - * @brief Asynchronously loads the favicon for the station. - * - * Loads from cache is not requesting reload and cache exists - * Otherwise async calls to the website to download the favicon, - * then stores it in the cache and replaces the Station favicon - * - * @param {bool} reload - Whether to force reload the favicon. - */ - private async void load_favicon_async( bool reload = false ) - { - debug(@"$(stationuuid) - Start - load_favicon_async for favicon: $(favicon)"); - - /* - Get favicon from cache file if file is in cache AND - Not requesting reload Or favicon is currently failing - */ - if ( ( !reload || STATION_FAILING_FAVICON.contains(stationuuid) ) - && FileUtils.test(_favicon_cache_file, FileTest.EXISTS)) - { - try { - var pixbuf = new Gdk.Pixbuf.from_file_at_scale(_favicon_cache_file, 48, 48, true); - _favicon_pixbuf = pixbuf; - debug(@"$(stationuuid) - Complete - load_favicon_async from cache stored in file://$(_favicon_cache_file)"); - favicon_loaded++; - station_favicon_sig(); - return; - } catch (Error e) { - info(@"$(stationuuid) - Failed to load cached favicon: $(e.message)"); - } - } - - if (_favicon_uri == null) - return; // First load or reload requested and favicon is not failing - - uint status_code; - - InputStream? stream = yield HttpClient.GETasync(_favicon_uri, Priority.LOW, out status_code); // Will automatically try several times - - if ( stream != null && status_code == 200 ) - /* - Input stream OK - */ - { - try { - var pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(stream, 48, 48, true,null); - pixbuf.save(_favicon_cache_file, "png"); - _favicon_pixbuf = pixbuf; - debug(@"$(stationuuid) - Complete - load_favicon_async from internet for $(_favicon_uri.to_string())\nStored in file://$(_favicon_cache_file)"); - favicon_loaded++; - station_favicon_sig(); - return; - - } catch (Error e) { - debug(@"$(stationuuid) - Failed to process favicon $(_favicon_uri.to_string()) - $(e.message)"); - } - } - info(@"$(stationuuid) - Failed to load favicon $(_favicon_uri.to_string()) - Status code: $(status_code)"); - STATION_FAILING_FAVICON.add(stationuuid); - } // load_favicon_async - - - /** - * @brief Asynchronously sets the given image to the station favicon, if available. - * - * Load the known icons first, and if reload, go around after trying the favicon url - * - * @param {Image} favicon_image - The favicon image to be updated. - * @param {bool} reload - Whether to force reload the favicon from source. - * @return {bool} True if the favicon was available and the image updated. - */ - public async bool update_favicon_image( Gtk.Image favicon_image, bool reload = false, string defaulticon = "") - { - bool reloading = false; - do { - try{ - if ( _favicon_pixbuf == null || STATION_FAILING_FAVICON.contains(stationuuid)) - { - yield fade(favicon_image, FADE_MS, false); - favicon_image.set_from_icon_name(defaulticon,Gtk.IconSize.DIALOG); - yield fade(favicon_image, FADE_MS, true); - } - else{ - - yield fade(favicon_image, FADE_MS, false); - favicon_image.set_from_pixbuf(_favicon_pixbuf); - yield fade(favicon_image, FADE_MS, true); - } - } finally { - favicon_image.opacity = 1; - reloading = false; - } - - if ( reload && favicon_loaded < 2 ) - /* - Reload requested, and favicon has not had reload requested before - */ - { - STATION_FAILING_FAVICON.remove(stationuuid); // Give possible 2nd chance - yield load_favicon_async(true); // Wait for load_favicon_async to complete - reloading = true; - reload = false; - } - } while ( reloading ); - return true; - } // update_favicon_image - - /** * @brief Sets the station up-to-date status based on the given station. * @@ -513,4 +384,21 @@ public class Tuner.Model.Station : Object { return STATIONS.get(stationuuid); } // updated + + + /** + * @brief Compares this station with another station. + * @param other The station to compare with. + * @return {bool} True if stations are equal. + */ + public bool equals(Station other) + { + return this.stationuuid == other.stationuuid; + } // equals + + + protected override string favicon_cache_file() + { + return _favicon_cache_file; + } // favicon_cache_file } // Station diff --git a/src/Models/StreamMetadata.vala b/src/Models/StreamMetadata.vala new file mode 100644 index 0000000..f27eeaa --- /dev/null +++ b/src/Models/StreamMetadata.vala @@ -0,0 +1,190 @@ +/** + * SPDX-FileCopyrightText: Copyright © 2020-2024 Louis Brauer + * SPDX-FileCopyrightText: Copyright © 2024 technosf + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * @file PlayerController.vala + */ + +using Gst; + +/** + * @class Metadata + * + * @brief Stream Metadata transform + * + */ +public class Tuner.Model.Metadata : GLib.Object +{ + private static string[,] METADATA_TITLES = + // Ordered array of tags and descriptions + { + {"title", _("Title") }, + {"artist", _("Artist") }, + {"album", _("Album") }, + {"image", _("Image") }, + {"genre", _("Genre") }, + {"homepage", _("Homepage") }, + {"organization", _("Organization") }, + {"location", _("Location") }, + {"extended-comment", _("Extended Comment") }, + {"bitrate", _("Bitrate") }, + {"audio-codec", _("Audio Codec") }, + {"channel-mode", _("Channel Mode") }, + {"track-number", _("Track Number") }, + {"track-count", _("Track Count") }, + {"nominal-bitrate", _("Nominal Bitrate") }, + {"minimum-bitrate", _("Minimum Bitrate") }, + {"maximum-bitrate", _("Maximim Bitrate") }, + {"container-format", ("Container Format") }, + {"application-name", _("Application Name") }, + {"encoder", _("Encoder") }, + {"encoder-version", _("Encoder Version") }, + {"datetime", _("Date Time") }, + {"private-data", _("Private Data") }, + {"has-crc", _("Has CRC") } + }; + + private static Gee.List METADATA_TAGS = new Gee.ArrayList (); + + static construct { + + uint8 tag_index = 0; + foreach ( var tag in METADATA_TITLES ) + // Replicating the order in METADATA_TITLES + { + if ((tag_index++)%2 == 0) + METADATA_TAGS.insert (tag_index/2, tag ); + } + } + + public string all_tags { get; private set; default = ""; } + public string title { get; private set; default = ""; } + public string artist { get; private set; default = ""; } + public string image { get; private set; default = ""; } + public string genre { get; private set; default = ""; } + public string homepage { get; private set; default = ""; } + public string audio_info { get; private set; default = ""; } + public string org_loc { get; private set; default = ""; } + public string pretty_print { get; private set; default = ""; } + + private Gee.Map _metadata_values = new Gee.HashMap(); // Hope it come out in order + + + /** + * Extracts the metadata from the media stream. + * + * @param media_info The media information stream + * @return true if the metadata has changed + */ + internal bool process_media_info_update (PlayerMediaInfo media_info) + { + var streamlist = media_info.get_stream_list ().copy (); + + title = ""; + artist = ""; + image = ""; + genre = ""; + homepage = ""; + audio_info = ""; + org_loc = ""; + pretty_print = ""; + + foreach (var stream in streamlist) // Hopefully just one metadata stream + { + var? tags = stream.get_tags (); // Get the raw tags + + if (tags == null) + break; // No tags, break on this metadata stream + + if (all_tags == tags.to_string ()) + return false; // Compare to all tags and if no change return false + + all_tags = tags.to_string (); + debug(@"All Tags: $all_tags"); + + string? s = null; + bool b = false; + uint u = 0; + + tags.foreach ((list, tag) => + { + var index = METADATA_TAGS.index_of (tag); + + if (index == -1) + { + warning(@"New meta tag: $tag"); + return; + } + + var type = (list.get_value_index(tag, 0)).type(); + + switch (type) + { + case GLib.Type.STRING: + list.get_string(tag, out s); + _metadata_values.set ( tag, s); + break; + case GLib.Type.UINT: + list.get_uint(tag, out u); + if ( u > 1000) + _metadata_values.set ( tag, @"$(u/1000)K"); + else + _metadata_values.set ( tag, u.to_string ()); + break; + case GLib.Type.BOOLEAN: + list.get_boolean (tag, out b); + _metadata_values.set ( tag, b.to_string ()); + break; + default: + warning(@"New Tag type: $(type.name())"); + break; + } + }); // tags.foreach + + if (_metadata_values.has_key ("title" )) + _title = _metadata_values.get ("title"); + if (_metadata_values.has_key ("artist" )) + _artist = _metadata_values.get ("artist"); + if (_metadata_values.has_key ("image" )) + _image = _metadata_values.get ("image"); + if (_metadata_values.has_key ("genre" )) + _genre = _metadata_values.get ("genre"); + if (_metadata_values.has_key ("homepage" )) + _homepage = _metadata_values.get ("homepage"); + + if (_metadata_values.has_key ("audio_codec" )) + _audio_info = _metadata_values.get ("audio_codec "); + if (_metadata_values.has_key ("bitrate" )) + _audio_info += _metadata_values.get ("bitrate "); + if (_metadata_values.has_key ("channel_mode" )) + _audio_info += _metadata_values.get ("channel_mode"); + if (_audio_info != null && _audio_info.length > 0) + _audio_info = safestrip(_audio_info); + + if (_metadata_values.has_key ("organization" )) + _org_loc = _metadata_values.get ("organization "); + if (_metadata_values.has_key ("location" )) + _org_loc += _metadata_values.get ("location"); + if (_org_loc != null && _org_loc.length > 0) + org_loc = safestrip(_org_loc); + + StringBuilder sb = new StringBuilder (); + foreach ( var tag in METADATA_TAGS ) + // Pretty print + { + if (_metadata_values.has_key(tag)) + { + sb.append ( METADATA_TITLES[METADATA_TAGS.index_of (tag),1]) + .append(" : ") + .append( _metadata_values.get (tag)) + .append("\n"); + } + } + pretty_print = sb.truncate (sb.len-1).str; + } // foreach + + return true; + } // process_media_info_update +} // Metadata diff --git a/src/Services/DBusMediaPlayer.vala b/src/Services/DBusMediaPlayer.vala index 1ec281c..250a432 100644 --- a/src/Services/DBusMediaPlayer.vala +++ b/src/Services/DBusMediaPlayer.vala @@ -171,7 +171,7 @@ namespace Tuner.DBus { trigger_metadata_update (); }); - app().player.shuffle_mode_sig.connect ((shuffle) => + app().shuffle_mode_sig.connect ((shuffle) => { _shuffle = shuffle; }); diff --git a/src/Widgets/Display.vala b/src/Widgets/Display.vala index e78829e..455c967 100644 --- a/src/Widgets/Display.vala +++ b/src/Widgets/Display.vala @@ -181,7 +181,7 @@ public class Tuner.Display : Gtk.Paned, StationListHookup { _saved_searches_category.expanded = false; _explore_category.collapsible = true; - _explore_category.expanded = false; + _explore_category.expanded = true; _genres_category.collapsible = true; _genres_category.expanded = false; @@ -561,7 +561,7 @@ public class Tuner.Display : Gtk.Paned, StationListHookup { { _shuffle = true; jukebox_shuffle.begin(); - app().player.shuffle_mode_sig(true); + app().shuffle_mode_sig(true); _background_tuner.reveal_child = false; _background_jukebox.reveal_child = true; }); @@ -589,7 +589,7 @@ public class Tuner.Display : Gtk.Paned, StationListHookup { if ( _shuffle ) { _shuffle = false; - app().player.shuffle_mode_sig(false); + app().shuffle_mode_sig(false); _background_jukebox.reveal_child = false; _background_tuner.reveal_child = true; } // if diff --git a/src/Widgets/HeaderBar.vala b/src/Widgets/HeaderBar.vala index 7444f04..131e0ad 100644 --- a/src/Widgets/HeaderBar.vala +++ b/src/Widgets/HeaderBar.vala @@ -539,7 +539,7 @@ public class Tuner.HeaderBar : Gtk.HeaderBar * * Desensitive when off-line */ - public void handle_metadata_changed ( Model.Station station, PlayerController.Metadata metadata ) + public void handle_metadata_changed ( Model.Station station, Model.Metadata metadata ) { if (_metadata == metadata.pretty_print) return; // No change diff --git a/src/Widgets/StationButton.vala b/src/Widgets/StationButton.vala index 4158228..a00a60c 100644 --- a/src/Widgets/StationButton.vala +++ b/src/Widgets/StationButton.vala @@ -78,12 +78,12 @@ public class Tuner.StationButton : Tuner.DisplayButton Set the button image. Connect to the flag that the Station has loaded the favicon and when it set, update the image. Check that if its already loaded, load now. */ - favicon_handler_id = station.station_favicon_sig.connect(() => + favicon_handler_id = station.favicon_sig.connect(() => { station.disconnect(favicon_handler_id); station.update_favicon_image.begin (_favicon_image); }); - if ( station.favicon_loaded > 0 ) + if ( station.favicon_loaded() > 0 ) { station.disconnect(favicon_handler_id); station.update_favicon_image.begin (_favicon_image); @@ -137,7 +137,6 @@ public class Tuner.StationButton : Tuner.DisplayButton { tag = tag + " " + bitrate.to_string() + "k"; } - return tag; } // make_tag diff --git a/src/meson.build b/src/meson.build index 16cf724..874e13b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -7,6 +7,8 @@ sources = files ( 'Models/Countries.vala', 'Models/Genre.vala', 'Models/Station.vala', + 'Models/Favicon.vala', + 'Models/StreamMetadata.vala', 'Providers/RadioBrowser.vala',