[vlc-devel] [PATCH 11/14] player: Save & restore playback states

Hugo Beauzée-Luyssen hugo at beauzee.fr
Mon Sep 23 11:44:54 CEST 2019


Refs #22524
---
 src/Makefile.am       |   1 +
 src/player/input.c    |  76 +++++++++++++
 src/player/medialib.c | 251 ++++++++++++++++++++++++++++++++++++++++++
 src/player/player.c   |   3 +-
 src/player/player.h   |  23 ++++
 src/player/track.c    |  13 +++
 6 files changed, 366 insertions(+), 1 deletion(-)
 create mode 100644 src/player/medialib.c

diff --git a/src/Makefile.am b/src/Makefile.am
index 9f0b6df13a..42d124127b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -270,6 +270,7 @@ libvlccore_la_SOURCES = \
 	player/aout.c \
 	player/vout.c \
 	player/osd.c \
+	player/medialib.c \
 	clock/input_clock.h \
 	clock/clock.h \
 	clock/clock_internal.h \
diff --git a/src/player/input.c b/src/player/input.c
index 74cfd3272d..3014916963 100644
--- a/src/player/input.c
+++ b/src/player/input.c
@@ -406,6 +406,32 @@ vlc_player_input_HandleEsEvent(struct vlc_player_input *input,
             }
             vlc_player_SendEvent(player, on_track_list_changed,
                                  VLC_PLAYER_LIST_ADDED, &trackpriv->t);
+            switch (ev->fmt->i_cat)
+            {
+                case VIDEO_ES:
+                    /* If we need to restore a specific track, let's do it upon
+                     * insertion. The initialization of the default track when
+                     * we don't have a value will be done when the first track
+                     * gets selected */
+                    if (input->ml.states.current_video_track != -2 &&
+                        input->ml.states.current_video_track == ev->fmt->i_id)
+                        vlc_player_SelectTrack(input->player, &trackpriv->t,
+                                               VLC_PLAYER_SELECT_EXCLUSIVE);
+                    break;
+                case AUDIO_ES:
+                    if (input->ml.states.current_audio_track != -2 &&
+                        input->ml.states.current_audio_track == ev->fmt->i_id)
+                        vlc_player_SelectTrack(input->player, &trackpriv->t,
+                                               VLC_PLAYER_SELECT_EXCLUSIVE);
+                    break;
+                case SPU_ES:
+                    if (input->ml.states.current_subtitle_track != -2 &&
+                        input->ml.states.current_subtitle_track == ev->fmt->i_id)
+                        vlc_player_SelectTrack(input->player, &trackpriv->t,
+                                               VLC_PLAYER_SELECT_EXCLUSIVE);
+                default:
+                    break;
+            }
             break;
         case VLC_INPUT_ES_DELETED:
         {
@@ -437,6 +463,26 @@ vlc_player_input_HandleEsEvent(struct vlc_player_input *input,
                 vlc_player_SendEvent(player, on_track_selection_changed,
                                      NULL, trackpriv->t.es_id);
             }
+            switch (ev->fmt->i_cat)
+            {
+                /* Save the default selected track to know if it changed
+                 * when the playback stops, in order to save the user's
+                 * explicitely selected track */
+                case VIDEO_ES:
+                    if (input->ml.default_video_track == -2)
+                        input->ml.default_video_track = ev->fmt->i_id;
+                    break;
+                case AUDIO_ES:
+                    if (input->ml.default_audio_track == -2)
+                        input->ml.default_audio_track = ev->fmt->i_id;
+                    break;
+                case SPU_ES:
+                    if (input->ml.default_subtitle_track == -2)
+                        input->ml.default_subtitle_track = ev->fmt->i_id;
+                    break;
+                default:
+                    break;
+            }
             break;
         case VLC_INPUT_ES_UNSELECTED:
             trackpriv = vlc_player_track_vector_FindById(vec, ev->id, NULL);
@@ -473,8 +519,15 @@ vlc_player_input_HandleTitleEvent(struct vlc_player_input *input,
                                              title_offset, chapter_offset);
             vlc_player_SendEvent(player, on_titles_changed, input->titles);
             if (input->titles)
+            {
                 vlc_player_SendEvent(player, on_title_selection_changed,
                                      &input->titles->array[0], 0);
+                if (input->ml.states.current_title >= 0 &&
+                    (size_t)input->ml.states.current_title < ev->list.count)
+                {
+                    vlc_player_SelectTitleIdx(player, input->ml.states.current_title);
+                }
+            }
             break;
         }
         case VLC_INPUT_TITLE_SELECTED:
@@ -485,6 +538,16 @@ vlc_player_input_HandleTitleEvent(struct vlc_player_input *input,
             vlc_player_SendEvent(player, on_title_selection_changed,
                                  &input->titles->array[input->title_selected],
                                  input->title_selected);
+            if (input->ml.states.current_title >= 0 &&
+                (size_t)input->ml.states.current_title == ev->selected_idx &&
+                input->ml.states.progress > .0f)
+            {
+                input_SetPosition(input->thread, input->ml.states.progress, false);
+                /* Reset the wanted title to avoid forcing it or the position
+                 * again during the next title change
+                 */
+                input->ml.states.current_title = 0;
+            }
             break;
         default:
             vlc_assert_unreachable();
@@ -711,6 +774,17 @@ vlc_player_input_New(vlc_player_t *player, input_item_t *item)
 
     input->abloop_state[0].set = input->abloop_state[1].set = false;
 
+    memset(&input->ml.states, 0, sizeof(input->ml.states));
+    input->ml.states.aspect_ratio = input->ml.states.crop =
+        input->ml.states.deinterlace = input->ml.states.video_filter = NULL;
+    input->ml.states.current_title = -1;
+    input->ml.states.current_video_track =
+        input->ml.states.current_audio_track =
+        input->ml.states.current_subtitle_track =
+        input->ml.default_video_track = input->ml.default_audio_track =
+        input->ml.default_subtitle_track = -2;
+    input->ml.states.progress = -1.f;
+
     input->thread = input_Create(player, input_thread_Events, input, item,
                                  player->resource, player->renderer);
     if (!input->thread)
@@ -719,6 +793,8 @@ vlc_player_input_New(vlc_player_t *player, input_item_t *item)
         return NULL;
     }
 
+    vlc_player_input_RestoreMlStates(input, item);
+
     /* Initial sub/audio delay */
     const vlc_tick_t cat_delays[DATA_ES] = {
         [AUDIO_ES] =
diff --git a/src/player/medialib.c b/src/player/medialib.c
new file mode 100644
index 0000000000..27d8364cd7
--- /dev/null
+++ b/src/player/medialib.c
@@ -0,0 +1,251 @@
+/*****************************************************************************
+ * medialib.c: Player/Media Library interractions
+ *****************************************************************************
+ * Copyright © 2018-2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include "player.h"
+#include "misc/variables.h"
+
+void
+vlc_player_input_RestoreMlStates(struct vlc_player_input* input,
+                                 const input_item_t* item)
+{
+    vlc_player_t* player = input->player;
+    vlc_player_assert_locked(player);
+
+    vlc_medialibrary_t* ml = vlc_ml_instance_get(input->player);
+    if (!ml)
+        return;
+    vlc_ml_media_t* media = vlc_ml_get_media_by_mrl( ml, item->psz_uri);
+    if (!media)
+        return;
+    if (vlc_ml_media_get_all_playback_pref(ml, media->i_id, &input->ml.states) != VLC_SUCCESS)
+        return;
+    /* If we are aiming at a specific title, wait for it to be added, and
+     * only then select it & set the position.
+     * If we're not aiming at a specific title, just set the position now.
+     */
+    if (input->ml.states.current_title == -1 && input->ml.states.progress > .0f)
+        input_SetPosition(input->thread, input->ml.states.progress, false);
+    if (input->ml.states.rate != .0f)
+        vlc_player_ChangeRate(player, input->ml.states.rate);
+
+    /* Tracks are restored upon insertion, except when explicitely disabled */
+    if (input->ml.states.current_video_track == -1)
+    {
+        input->ml.default_video_track = -1;
+        input_Control(input->thread, INPUT_CONTROL_SET_ES_AUTOSELECT,
+                        &(input_control_param_t) {
+                            .es_autoselect.cat = VIDEO_ES,
+                            .es_autoselect.enabled = false,
+                        });
+    }
+    if (input->ml.states.current_audio_track == -1)
+    {
+        input->ml.default_audio_track = -1;
+        input_Control(input->thread, INPUT_CONTROL_SET_ES_AUTOSELECT,
+                        &(input_control_param_t) {
+                            .es_autoselect.cat = AUDIO_ES,
+                            .es_autoselect.enabled = false,
+                        });
+    }
+    if (input->ml.states.current_subtitle_track == -1)
+    {
+        input->ml.default_subtitle_track = -1;
+        input_Control(input->thread, INPUT_CONTROL_SET_ES_AUTOSELECT,
+                        &(input_control_param_t) {
+                            .es_autoselect.cat = SPU_ES,
+                            .es_autoselect.enabled = false,
+                        });
+    }
+
+    vout_thread_t* vout = vlc_player_vout_Hold(player);
+    if (vout != NULL)
+    {
+        if (input->ml.states.zoom >= .0f)
+            var_SetFloat(vout, "zoom", input->ml.states.zoom);
+        else
+            var_SetFloat(vout, "zoom", 1.f);
+        if (input->ml.states.aspect_ratio)
+            var_SetString(vout, "aspect-ratio", input->ml.states.aspect_ratio);
+        else
+            var_SetString(vout, "aspect-ratio", NULL);
+        if (input->ml.states.deinterlace)
+        {
+            var_SetString(vout, "deinterlace-mode", input->ml.states.deinterlace);
+            var_SetInteger(vout, "deinterlace", 1);
+        }
+        else
+        {
+            var_SetString(vout, "deinterlace-mode", NULL);
+            var_SetInteger(vout, "deinterlace", 0);
+        }
+        if (input->ml.states.video_filter)
+            var_SetString(vout, "video-filter", input->ml.states.video_filter);
+        else
+            var_SetString(vout, "video-filter", NULL);
+        vout_Release(vout);
+    }
+    vlc_ml_release(media);
+}
+
+void
+vlc_player_UpdateMLStates(vlc_player_t *player, struct vlc_player_input* input)
+{
+    /* Do not save states for any secondary player. If this player's parent is
+       the main vlc object, then it's the main player */
+    if (player->obj.priv->parent != (vlc_object_t*)vlc_object_instance(player))
+        return;
+
+    vlc_medialibrary_t* ml = vlc_ml_instance_get(player);
+    if (!ml)
+        return;
+    input_item_t* item = input_GetItem(input->thread);
+    if (!item)
+        return;
+    vlc_ml_media_t* media = vlc_ml_get_media_by_mrl(ml, item->psz_uri);
+    if (!media)
+    {
+        /* We don't know this media yet, let's add it as an external media so
+         * we can still store its playback preferences
+         */
+        media = vlc_ml_new_external_media(ml, item->psz_uri);
+        if (media == NULL)
+            return;
+    }
+
+    input->ml.states.progress = input->position;
+
+    /* If the value changed during the playback, update it in the medialibrary.
+     * If not, set each state to their "unset" values, so that they aren't saved
+     * in database */
+    if ((input->ml.states.current_title == -1 && input->title_selected != 0) ||
+         (input->ml.states.current_title != -1 &&
+          input->ml.states.current_title != (int)input->title_selected))
+    {
+        input->ml.states.current_title = input->title_selected;
+    }
+    else
+        input->ml.states.current_title = -1;
+
+    /* We use .0f to signal an unsaved rate. We want to save it to the ml if it
+     * changed, and if it's not the player's default value when the value was
+     * never saved in the ML */
+    if (input->rate != input->ml.states.rate &&
+            (input->rate != 1.f || input->ml.states.rate != .0f))
+        input->ml.states.rate = input->rate;
+    else
+        input->ml.states.rate = -1.f;
+
+    struct vlc_player_track_priv* t;
+    vout_thread_t* vout = NULL;
+
+    vlc_vector_foreach(t, &input->video_track_vector)
+    {
+        if (!t->t.selected)
+            continue;
+        enum vlc_vout_order order;
+        vout = vlc_player_GetEsIdVout(player, t->t.es_id, &order);
+        if (vout != NULL && order == VLC_VOUT_ORDER_PRIMARY)
+            break;
+        vout = NULL;
+    }
+    if (vout != NULL)
+    {
+        /* We only want to save these states if they are different, and not the
+         * default values (NULL), so this means that either one is NULL and the
+         * other isn't, or they are both non null and differ lexicographically */
+#define COMPARE_ASSIGN_STR(field, var) \
+        char* field = var_GetNonEmptyString(vout, var); \
+        if ( ( field != NULL && input->ml.states.field != NULL && \
+               strcmp(field, input->ml.states.field) ) || \
+             ( field == NULL && input->ml.states.field != NULL ) || \
+             ( field != NULL && input->ml.states.field == NULL ) ) \
+        { \
+            free(input->ml.states.field); \
+            input->ml.states.field = field; \
+            field = NULL; \
+        } \
+        else \
+        { \
+            free(input->ml.states.field); \
+            input->ml.states.field = NULL; \
+        }
+
+        COMPARE_ASSIGN_STR(aspect_ratio, "aspect-ratio" );
+        COMPARE_ASSIGN_STR(crop, "crop");
+        COMPARE_ASSIGN_STR(deinterlace, "deinterlace-mode");
+        COMPARE_ASSIGN_STR(video_filter, "video-filter");
+
+        if (input->ml.states.deinterlace != NULL &&
+            !strcmp(input->ml.states.deinterlace, "auto"))
+        {
+            free(input->ml.states.deinterlace);
+            input->ml.states.deinterlace = NULL;
+        }
+
+        float zoom = var_GetFloat(vout, "zoom");
+        if (zoom != input->ml.states.zoom &&
+            (zoom != 1.f && input->ml.states.zoom >= .0f))
+            input->ml.states.zoom = zoom;
+        else
+            input->ml.states.zoom = -1.f;
+
+#undef COMPARE_ASSIGN_STR
+        free(video_filter);
+        free(deinterlace);
+        free(crop);
+        free(aspect_ratio);
+    }
+
+    if (input->ml.default_video_track != -2)
+    {
+        int current_video_track = vlc_player_GetFirstSelectedTrackId(&input->video_track_vector);
+        if (input->ml.default_video_track != current_video_track)
+            input->ml.states.current_video_track = current_video_track;
+        else
+            input->ml.states.current_video_track = -2;
+    }
+
+    if (input->ml.default_audio_track != -2)
+    {
+        int current_audio_track = vlc_player_GetFirstSelectedTrackId(&input->audio_track_vector);
+        if (input->ml.default_audio_track != current_audio_track)
+            input->ml.states.current_audio_track = current_audio_track;
+        else
+            input->ml.states.current_audio_track = -2;
+    }
+
+    if (input->ml.default_subtitle_track != -2)
+    {
+        int current_subtitle_track = vlc_player_GetFirstSelectedTrackId(&input->spu_track_vector);
+        if (input->ml.default_subtitle_track != current_subtitle_track)
+            input->ml.states.current_subtitle_track = current_subtitle_track;
+        else
+            input->ml.states.current_subtitle_track = -2;
+    }
+
+    vlc_ml_media_set_all_playback_states(ml, media->i_id, &input->ml.states);
+    vlc_ml_release(&input->ml.states);
+    vlc_ml_release(media);
+}
diff --git a/src/player/player.c b/src/player/player.c
index 839fd0eea7..396242701c 100644
--- a/src/player/player.c
+++ b/src/player/player.c
@@ -54,7 +54,7 @@ vlc_player_PrepareNextMedia(vlc_player_t *player)
 {
     vlc_player_assert_locked(player);
 
-    if (!player->media_provider 
+    if (!player->media_provider
      || player->media_stopped_action != VLC_PLAYER_MEDIA_STOPPED_CONTINUE
      || player->next_media_requested)
         return;
@@ -206,6 +206,7 @@ vlc_player_destructor_Thread(void *data)
             vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPING);
             vlc_player_destructor_AddStoppingInput(player, input);
 
+            vlc_player_UpdateMLStates(player, input);
             input_Stop(input->thread);
         }
 
diff --git a/src/player/player.h b/src/player/player.h
index c75eb9e97a..bdf77a0ac5 100644
--- a/src/player/player.h
+++ b/src/player/player.h
@@ -25,6 +25,7 @@
 #include <vlc_list.h>
 #include <vlc_vector.h>
 #include <vlc_atomic.h>
+#include <vlc_media_library.h>
 
 #include "input/input_internal.h"
 
@@ -98,6 +99,14 @@ struct vlc_player_input
         float pos;
         bool set;
     } abloop_state[2];
+
+    struct
+    {
+        vlc_ml_playback_states_all states;
+        int default_video_track;
+        int default_audio_track;
+        int default_subtitle_track;
+    } ml;
 };
 
 struct vlc_player_listener_id
@@ -264,6 +273,9 @@ struct vlc_player_track_priv *
 vlc_player_track_vector_FindById(vlc_player_track_vector *vec, vlc_es_id_t *id,
                                  size_t *idx);
 
+int
+vlc_player_GetFirstSelectedTrackId(const vlc_player_track_vector* tracks);
+
 /*
  * player_title.c
  */
@@ -362,4 +374,15 @@ vlc_player_osd_Track(vlc_player_t *player, vlc_es_id_t *id, bool select);
 void
 vlc_player_osd_Program(vlc_player_t *player, const char *name);
 
+/*
+ * player/medialib.c
+ */
+
+void
+vlc_player_input_RestoreMlStates(struct vlc_player_input* input,
+                                 const input_item_t* item);
+
+void
+vlc_player_UpdateMLStates(vlc_player_t *player, struct vlc_player_input* input);
+
 #endif
diff --git a/src/player/track.c b/src/player/track.c
index 4438ce8031..b02f8d5881 100644
--- a/src/player/track.c
+++ b/src/player/track.c
@@ -205,3 +205,16 @@ vlc_player_track_vector_FindById(vlc_player_track_vector *vec, vlc_es_id_t *id,
     }
     return NULL;
 }
+
+
+int
+vlc_player_GetFirstSelectedTrackId(const vlc_player_track_vector* tracks)
+{
+    struct vlc_player_track_priv* t;
+    vlc_vector_foreach(t, tracks)
+    {
+        if (t->t.selected)
+            return t->t.fmt.i_id;
+    }
+    return -1;
+}
-- 
2.20.1



More information about the vlc-devel mailing list