11/*
22 * SPDX-License-Identifier: LGPL-3.0-or-later
3- * SPDX-FileCopyrightText: 2021-2022 elementary, Inc. (https://elementary.io)
3+ * SPDX-FileCopyrightText: 2021-2026 elementary, Inc. (https://elementary.io)
44 */
55
66public class Music.PlaybackManager : Object {
@@ -14,6 +14,7 @@ public class Music.PlaybackManager : Object {
1414 }
1515 public int64 playback_position { get ; private set ; }
1616 public signal void invalids_found (int count );
17+ public signal void duplicates_found (int count );
1718
1819 private static GLib . Once<PlaybackManager > instance;
1920 public static unowned PlaybackManager get_default () {
@@ -39,6 +40,7 @@ public class Music.PlaybackManager : Object {
3940 private SimpleAction play_pause_action;
4041 private SimpleAction previous_action;
4142 private SimpleAction shuffle_action;
43+ private SimpleAction save_playlist_action;
4244
4345 private PlaybackManager () {}
4446
@@ -75,19 +77,25 @@ public class Music.PlaybackManager : Object {
7577 shuffle_action = new SimpleAction (Application . ACTION_SHUFFLE , null );
7678 shuffle_action. activate. connect (shuffle);
7779
80+ save_playlist_action = new SimpleAction (Application . ACTION_SAVE_TO_PLAYLIST , null );
81+ save_playlist_action. activate. connect (save_queue_to_playlist);
82+
7883 next_action. set_enabled (false );
7984 play_pause_action. set_enabled (false );
8085 previous_action. set_enabled (false );
8186 shuffle_action. set_enabled (false );
87+ save_playlist_action. set_enabled (false );
8288
8389 unowned var app = GLib . Application . get_default ();
8490 app. add_action (clear_action);
8591 app. add_action (next_action);
8692 app. add_action (play_pause_action);
8793 app. add_action (previous_action);
8894 app. add_action (shuffle_action);
95+ app. add_action (save_playlist_action);
8996
9097 bind_property (" has-items" , clear_action, " enabled" , SYNC_CREATE );
98+ bind_property (" has-items" , save_playlist_action, " enabled" , SYNC_CREATE );
9199 }
92100
93101 public void seek_to_progress (double percent ) {
@@ -99,10 +107,26 @@ public class Music.PlaybackManager : Object {
99107 // AudioObjects start discovering metadata asynchronously on creation
100108 public void queue_files (File [] files ) {
101109 int invalids = 0 ;
110+ int duplicates = 0 ;
111+ int added_tracks = 0 ;
102112 foreach (unowned var file in files) {
103113 if (file. query_exists () && " audio" in ContentType . guess (file. get_uri (), null , null )) {
104- var audio_object = new AudioObject (file. get_uri ());
105- queue_liststore. append (audio_object);
114+ uint pos = 0 ;
115+ bool found = false ;
116+ Object ? item = queue_liststore. get_item (pos++ );
117+ while (item != null && ! found) {
118+ if (((AudioObject )item). uri == file. get_uri ()) {
119+ found = true ;
120+ }
121+
122+ item = queue_liststore. get_item (pos++ );
123+ }
124+ if (! found) {
125+ queue_liststore. append (new AudioObject (file. get_uri ()));
126+ added_tracks++ ;
127+ } else {
128+ duplicates++ ;
129+ }
106130 } else {
107131 invalids++ ;
108132 continue ;
@@ -112,6 +136,9 @@ public class Music.PlaybackManager : Object {
112136 if (invalids > 0 ) {
113137 invalids_found (invalids);
114138 }
139+ if (duplicates > 0 ) {
140+ duplicates_found (duplicates);
141+ }
115142
116143 if (current_audio == null ) {
117144 var audio_object = (AudioObject ) queue_liststore. get_object (0 );
@@ -121,7 +148,6 @@ public class Music.PlaybackManager : Object {
121148 } else {
122149 // Don't notify on app startup or if the app is focused
123150 var application = (Gtk . Application ) GLib . Application . get_default ();
124- var added_tracks = files. length - invalids;
125151 if (
126152 ! application. get_active_window (). is_active &&
127153 added_tracks > 0
@@ -417,4 +443,56 @@ public class Music.PlaybackManager : Object {
417443 current_audio = (AudioObject ) queue_liststore. get_item (position);
418444 }
419445 }
446+
447+ public void save_queue_to_playlist () {
448+ var all_files_filter = new Gtk .FileFilter () {
449+ name = _ ("All files "),
450+ };
451+ all_files_filter.add_pattern ("*");
452+
453+ var playlist_filter = new Gtk .FileFilter () {
454+ name = _ ("Playlist files "),
455+ };
456+ playlist_filter.add_mime_type ("audio /x -mpegurl ");
457+
458+ var filter_model = new ListStore (typeof (Gtk . FileFilter ));
459+ filter_model.append (all_files_filter );
460+ filter_model.append (playlist_filter );
461+
462+ var save_dialog = new Gtk .FileDialog () {
463+ accept_label = _ ("Save "),
464+ default_filter = playlist_filter,
465+ filters = filter_model,
466+ modal = true,
467+ title = _ ("Save queue to playlist "),
468+ initial_name = "%s.m3u".printf (_ ("New Playlist ")),
469+ };
470+
471+ save_dialog.save.begin (null , null , (obj , res ) => {
472+ try {
473+ File ? file;
474+ file = save_dialog. save. end (res);
475+
476+ PlaylistObject playlist = new PlaylistObject (file);
477+ playlist. save_playlist (queue_liststore);
478+ } catch (Error err) {
479+ if (err. matches (Gtk . DialogError . quark (), Gtk . DialogError . DISMISSED )) {
480+ return ;
481+ }
482+
483+ warning (" Failed to save playlist: %s " , err. message);
484+
485+ var dialog = new Granite .MessageDialog (
486+ _(" Could not save playlist" ),
487+ err. message,
488+ new ThemedIcon (" audio-x-playlist" )
489+ ) {
490+ badge_icon = new ThemedIcon (" dialog-error" ),
491+ modal = true
492+ };
493+ dialog. present ();
494+ dialog. response. connect (dialog. destroy);
495+ }
496+ });
497+ }
420498}
0 commit comments