diff --git a/connect/Cargo.toml b/connect/Cargo.toml
index ee076c3e8..74b21b7dc 100644
--- a/connect/Cargo.toml
+++ b/connect/Cargo.toml
@@ -12,7 +12,7 @@ edition = "2021"
 futures-util = "0.3"
 log = "0.4"
 protobuf = "3.5"
-rand = "0.8"
+rand = { version = "0.8", default-features = false, features = ["small_rng"] }
 serde_json = "1.0"
 thiserror = "2.0"
 tokio = { version = "1", features = ["macros", "parking_lot", "sync"] }
diff --git a/connect/src/context_resolver.rs b/connect/src/context_resolver.rs
index 278fc0899..79d48973a 100644
--- a/connect/src/context_resolver.rs
+++ b/connect/src/context_resolver.rs
@@ -4,13 +4,10 @@ use crate::{
         autoplay_context_request::AutoplayContextRequest, context::Context,
         transfer_state::TransferState,
     },
-    state::{
-        context::{ContextType, UpdateContext},
-        ConnectState,
-    },
+    state::{context::ContextType, ConnectState},
 };
-use std::cmp::PartialEq;
 use std::{
+    cmp::PartialEq,
     collections::{HashMap, VecDeque},
     fmt::{Display, Formatter},
     hash::Hash,
@@ -35,7 +32,7 @@ pub(super) enum ContextAction {
 pub(super) struct ResolveContext {
     resolve: Resolve,
     fallback: Option<String>,
-    update: UpdateContext,
+    update: ContextType,
     action: ContextAction,
 }
 
@@ -44,7 +41,7 @@ impl ResolveContext {
         Self {
             resolve: Resolve::Uri(uri.into()),
             fallback: None,
-            update: UpdateContext::Default,
+            update: ContextType::Default,
             action: ContextAction::Append,
         }
     }
@@ -52,7 +49,7 @@ impl ResolveContext {
     pub fn from_uri(
         uri: impl Into<String>,
         fallback: impl Into<String>,
-        update: UpdateContext,
+        update: ContextType,
         action: ContextAction,
     ) -> Self {
         let fallback_uri = fallback.into();
@@ -64,7 +61,7 @@ impl ResolveContext {
         }
     }
 
-    pub fn from_context(context: Context, update: UpdateContext, action: ContextAction) -> Self {
+    pub fn from_context(context: Context, update: ContextType, action: ContextAction) -> Self {
         Self {
             resolve: Resolve::Context(context),
             fallback: None,
@@ -214,7 +211,7 @@ impl ContextResolver {
         let (next, resolve_uri, _) = self.find_next().ok_or(ContextResolverError::NoNext)?;
 
         match next.update {
-            UpdateContext::Default => {
+            ContextType::Default => {
                 let mut ctx = self.session.spclient().get_context(resolve_uri).await;
                 if let Ok(ctx) = ctx.as_mut() {
                     ctx.uri = Some(next.context_uri().to_string());
@@ -223,7 +220,7 @@ impl ContextResolver {
 
                 ctx
             }
-            UpdateContext::Autoplay => {
+            ContextType::Autoplay => {
                 if resolve_uri.contains("spotify:show:") || resolve_uri.contains("spotify:episode:")
                 {
                     // autoplay is not supported for podcasts
@@ -304,13 +301,13 @@ impl ContextResolver {
         }
 
         match (next.update, state.active_context) {
-            (UpdateContext::Default, ContextType::Default) | (UpdateContext::Autoplay, _) => {
+            (ContextType::Default, ContextType::Default) | (ContextType::Autoplay, _) => {
                 debug!(
                     "last item of type <{:?}>, finishing state setup",
                     next.update
                 );
             }
-            (UpdateContext::Default, _) => {
+            (ContextType::Default, _) => {
                 debug!("skipped finishing default, because it isn't the active context");
                 return false;
             }
@@ -320,7 +317,7 @@ impl ContextResolver {
         let res = if let Some(transfer_state) = transfer_state.take() {
             state.finish_transfer(transfer_state)
         } else if state.shuffling_context() {
-            state.shuffle()
+            state.shuffle(None)
         } else if matches!(active_ctx, Ok(ctx) if ctx.index.track == 0) {
             // has context, and context is not touched
             // when the index is not zero, the next index was already evaluated elsewhere
diff --git a/connect/src/lib.rs b/connect/src/lib.rs
index 11a651863..ebceaaac3 100644
--- a/connect/src/lib.rs
+++ b/connect/src/lib.rs
@@ -7,5 +7,6 @@ use librespot_protocol as protocol;
 
 mod context_resolver;
 mod model;
+pub mod shuffle_vec;
 pub mod spirc;
 pub mod state;
diff --git a/connect/src/shuffle_vec.rs b/connect/src/shuffle_vec.rs
new file mode 100644
index 000000000..b7bb5f3d5
--- /dev/null
+++ b/connect/src/shuffle_vec.rs
@@ -0,0 +1,117 @@
+use rand::{rngs::SmallRng, Rng, SeedableRng};
+use std::{
+    ops::{Deref, DerefMut},
+    vec::IntoIter,
+};
+
+#[derive(Debug, Clone, Default)]
+pub struct ShuffleVec<T> {
+    vec: Vec<T>,
+    indices: Option<Vec<usize>>,
+}
+
+impl<T: PartialEq> PartialEq for ShuffleVec<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.vec == other.vec
+    }
+}
+
+impl<T> Deref for ShuffleVec<T> {
+    type Target = Vec<T>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.vec
+    }
+}
+
+impl<T> DerefMut for ShuffleVec<T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.vec.as_mut()
+    }
+}
+
+impl<T> IntoIterator for ShuffleVec<T> {
+    type Item = T;
+    type IntoIter = IntoIter<T>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.vec.into_iter()
+    }
+}
+
+impl<T> From<Vec<T>> for ShuffleVec<T> {
+    fn from(vec: Vec<T>) -> Self {
+        Self { vec, indices: None }
+    }
+}
+
+impl<T> ShuffleVec<T> {
+    pub fn new() -> Self {
+        Self {
+            vec: Vec::new(),
+            indices: None,
+        }
+    }
+
+    pub fn shuffle_with_seed(&mut self, seed: u64) {
+        self.shuffle_with_rng(SmallRng::seed_from_u64(seed))
+    }
+
+    pub fn shuffle_with_rng(&mut self, mut rng: impl Rng) {
+        if self.indices.is_some() {
+            self.unshuffle()
+        }
+
+        let indices = {
+            (1..self.vec.len())
+                .rev()
+                .map(|i| rng.gen_range(0..i + 1))
+                .collect()
+        };
+
+        for (i, &rnd_ind) in (1..self.vec.len()).rev().zip(&indices) {
+            self.vec.swap(i, rnd_ind);
+        }
+
+        self.indices = Some(indices)
+    }
+
+    pub fn unshuffle(&mut self) {
+        let indices = match self.indices.take() {
+            Some(indices) => indices,
+            None => return,
+        };
+
+        for i in 1..self.vec.len() {
+            let n = indices[self.vec.len() - i - 1];
+            self.vec.swap(n, i);
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use rand::Rng;
+
+    #[test]
+    fn test_shuffle_with_seed() {
+        let seed = rand::thread_rng().gen_range(0..10000000000000);
+
+        let vec = (0..100).collect::<Vec<_>>();
+        let base_vec: ShuffleVec<i32> = vec.into();
+
+        let mut shuffled_vec = base_vec.clone();
+        shuffled_vec.shuffle_with_seed(seed);
+
+        let mut different_shuffled_vec = base_vec.clone();
+        different_shuffled_vec.shuffle_with_seed(seed);
+
+        assert_eq!(shuffled_vec, different_shuffled_vec);
+
+        let mut unshuffled_vec = shuffled_vec.clone();
+        unshuffled_vec.unshuffle();
+
+        assert_eq!(base_vec, unshuffled_vec);
+    }
+}
diff --git a/connect/src/spirc.rs b/connect/src/spirc.rs
index d4773fc04..d1cf9e5b2 100644
--- a/connect/src/spirc.rs
+++ b/connect/src/spirc.rs
@@ -25,9 +25,7 @@ use crate::{
         user_attributes::UserAttributesMutation,
     },
     state::{
-        context::{
-            ResetContext, {ContextType, UpdateContext},
-        },
+        context::{ContextType, ResetContext},
         metadata::Metadata,
         provider::IsProvider,
         {ConnectState, ConnectStateConfig},
@@ -37,7 +35,6 @@ use futures_util::StreamExt;
 use protobuf::MessageField;
 use std::{
     future::Future,
-    ops::Deref,
     sync::atomic::{AtomicUsize, Ordering},
     sync::Arc,
     time::{Duration, SystemTime, UNIX_EPOCH},
@@ -749,9 +746,6 @@ impl SpircTask {
 
         use protobuf::Message;
 
-        // todo: handle received pages from transfer, important to not always shuffle the first 10 tracks
-        //  also important when the dealer is restarted, currently we just shuffle again, but at least
-        //  the 10 tracks provided should be used and after that the new shuffle context
         match TransferState::parse_from_bytes(&cluster.transfer_data) {
             Ok(transfer_state) => self.handle_transfer(transfer_state)?,
             Err(why) => error!("failed to take over control: {why}"),
@@ -889,7 +883,7 @@ impl SpircTask {
                 } else {
                     self.context_resolver.add(ResolveContext::from_context(
                         update_context.context,
-                        super::state::context::UpdateContext::Default,
+                        ContextType::Default,
                         ContextAction::Replace,
                     ))
                 }
@@ -1007,7 +1001,7 @@ impl SpircTask {
         self.context_resolver.add(ResolveContext::from_uri(
             ctx_uri.clone(),
             &fallback,
-            UpdateContext::Default,
+            ContextType::Default,
             ContextAction::Replace,
         ));
 
@@ -1044,7 +1038,7 @@ impl SpircTask {
             self.context_resolver.add(ResolveContext::from_uri(
                 ctx_uri,
                 fallback,
-                UpdateContext::Autoplay,
+                ContextType::Autoplay,
                 ContextAction::Replace,
             ))
         }
@@ -1139,13 +1133,12 @@ impl SpircTask {
         };
 
         let update_context = if cmd.autoplay {
-            UpdateContext::Autoplay
+            ContextType::Autoplay
         } else {
-            UpdateContext::Default
+            ContextType::Default
         };
 
-        self.connect_state
-            .set_active_context(*update_context.deref());
+        self.connect_state.set_active_context(update_context);
 
         let current_context_uri = self.connect_state.context_uri();
         if current_context_uri == &cmd.context_uri && fallback == cmd.context_uri {
@@ -1209,7 +1202,7 @@ impl SpircTask {
             if self.context_resolver.has_next() {
                 self.connect_state.update_queue_revision()
             } else {
-                self.connect_state.shuffle()?;
+                self.connect_state.shuffle(None)?;
                 self.add_autoplay_resolving_when_required();
             }
         } else {
@@ -1366,7 +1359,7 @@ impl SpircTask {
         let resolve = ResolveContext::from_uri(
             current_context,
             fallback,
-            UpdateContext::Autoplay,
+            ContextType::Autoplay,
             if has_tracks {
                 ContextAction::Append
             } else {
@@ -1458,7 +1451,7 @@ impl SpircTask {
         self.context_resolver.add(ResolveContext::from_uri(
             uri,
             self.connect_state.current_track(|t| &t.uri),
-            UpdateContext::Default,
+            ContextType::Default,
             ContextAction::Replace,
         ));
 
diff --git a/connect/src/state.rs b/connect/src/state.rs
index c06618ae7..73010b257 100644
--- a/connect/src/state.rs
+++ b/connect/src/state.rs
@@ -7,12 +7,12 @@ mod restrictions;
 mod tracks;
 mod transfer;
 
-use crate::model::SpircPlayStatus;
 use crate::{
     core::{
         config::DeviceType, date::Date, dealer::protocol::Request, spclient::SpClientResult,
         version, Error, Session,
     },
+    model::SpircPlayStatus,
     protocol::{
         connect::{Capabilities, Device, DeviceInfo, MemberType, PutStateReason, PutStateRequest},
         media::AudioQuality,
@@ -26,7 +26,6 @@ use crate::{
         provider::{IsProvider, Provider},
     },
 };
-
 use log::LevelFilter;
 use protobuf::{EnumOrUnknown, MessageField};
 use std::{
@@ -118,10 +117,9 @@ pub struct ConnectState {
 
     /// the context from which we play, is used to top up prev and next tracks
     context: Option<StateContext>,
+    /// seed extracted in [ConnectState::handle_initial_transfer] and used in [ConnectState::finish_transfer]
+    transfer_shuffle_seed: Option<u64>,
 
-    /// a context to keep track of our shuffled context,
-    /// should be only available when `player.option.shuffling_context` is true
-    shuffle_context: Option<StateContext>,
     /// a context to keep track of the autoplay context
     autoplay_context: Option<StateContext>,
 }
diff --git a/connect/src/state/context.rs b/connect/src/state/context.rs
index fa78180a4..5233795ed 100644
--- a/connect/src/state/context.rs
+++ b/connect/src/state/context.rs
@@ -7,6 +7,7 @@ use crate::{
         player::{ContextIndex, ProvidedTrack},
         restrictions::Restrictions,
     },
+    shuffle_vec::ShuffleVec,
     state::{
         metadata::Metadata,
         provider::{IsProvider, Provider},
@@ -15,46 +16,28 @@ use crate::{
 };
 use protobuf::MessageField;
 use std::collections::HashMap;
-use std::ops::Deref;
 use uuid::Uuid;
 
 const LOCAL_FILES_IDENTIFIER: &str = "spotify:local-files";
 const SEARCH_IDENTIFIER: &str = "spotify:search";
 
-#[derive(Debug, Clone)]
+#[derive(Debug)]
 pub struct StateContext {
-    pub tracks: Vec<ProvidedTrack>,
+    pub tracks: ShuffleVec<ProvidedTrack>,
+    pub skip_track: Option<ProvidedTrack>,
     pub metadata: HashMap<String, String>,
     pub restrictions: Option<Restrictions>,
     /// is used to keep track which tracks are already loaded into the next_tracks
     pub index: ContextIndex,
 }
 
-#[derive(Default, Debug, Copy, Clone, PartialEq)]
+#[derive(Default, Debug, Copy, Clone, PartialEq, Hash, Eq)]
 pub enum ContextType {
     #[default]
     Default,
-    Shuffle,
     Autoplay,
 }
 
-#[derive(Debug, Hash, Copy, Clone, PartialEq, Eq)]
-pub enum UpdateContext {
-    Default,
-    Autoplay,
-}
-
-impl Deref for UpdateContext {
-    type Target = ContextType;
-
-    fn deref(&self) -> &Self::Target {
-        match self {
-            UpdateContext::Default => &ContextType::Default,
-            UpdateContext::Autoplay => &ContextType::Autoplay,
-        }
-    }
-}
-
 pub enum ResetContext<'s> {
     Completely,
     DefaultIndex,
@@ -96,12 +79,19 @@ impl ConnectState {
     pub fn get_context(&self, ty: ContextType) -> Result<&StateContext, StateError> {
         match ty {
             ContextType::Default => self.context.as_ref(),
-            ContextType::Shuffle => self.shuffle_context.as_ref(),
             ContextType::Autoplay => self.autoplay_context.as_ref(),
         }
         .ok_or(StateError::NoContext(ty))
     }
 
+    pub fn get_context_mut(&mut self, ty: ContextType) -> Result<&mut StateContext, StateError> {
+        match ty {
+            ContextType::Default => self.context.as_mut(),
+            ContextType::Autoplay => self.autoplay_context.as_mut(),
+        }
+        .ok_or(StateError::NoContext(ty))
+    }
+
     pub fn context_uri(&self) -> &String {
         &self.player().context_uri
     }
@@ -115,14 +105,18 @@ impl ConnectState {
         if matches!(reset_as, ResetContext::WhenDifferent(ctx) if self.different_context_uri(ctx)) {
             reset_as = ResetContext::Completely
         }
-        self.shuffle_context = None;
+
+        if let Ok(ctx) = self.get_context_mut(ContextType::Default) {
+            ctx.remove_shuffle_seed();
+            ctx.tracks.unshuffle()
+        }
 
         match reset_as {
+            ResetContext::WhenDifferent(_) => debug!("context didn't change, no reset"),
             ResetContext::Completely => {
                 self.context = None;
                 self.autoplay_context = None;
             }
-            ResetContext::WhenDifferent(_) => debug!("context didn't change, no reset"),
             ResetContext::DefaultIndex => {
                 for ctx in [self.context.as_mut(), self.autoplay_context.as_mut()]
                     .into_iter()
@@ -190,7 +184,7 @@ impl ConnectState {
     pub fn update_context(
         &mut self,
         mut context: Context,
-        ty: UpdateContext,
+        ty: ContextType,
     ) -> Result<Option<Vec<String>>, Error> {
         if context.pages.iter().all(|p| p.tracks.is_empty()) {
             error!("context didn't have any tracks: {context:#?}");
@@ -221,12 +215,13 @@ impl ConnectState {
         );
 
         match ty {
-            UpdateContext::Default => {
+            ContextType::Default => {
                 let mut new_context = self.state_context_from_page(
                     page,
                     context.metadata,
                     context.restrictions.take(),
                     context.uri.as_deref(),
+                    Some(0),
                     None,
                 );
 
@@ -245,7 +240,7 @@ impl ConnectState {
                         };
 
                         // enforce reloading the context
-                        if let Some(autoplay_ctx) = self.autoplay_context.as_mut() {
+                        if let Ok(autoplay_ctx) = self.get_context_mut(ContextType::Autoplay) {
                             autoplay_ctx.index.track = 0
                         }
                         self.clear_next_tracks();
@@ -261,12 +256,13 @@ impl ConnectState {
                 }
                 self.player_mut().context_uri = context.uri.take().unwrap_or_default();
             }
-            UpdateContext::Autoplay => {
+            ContextType::Autoplay => {
                 self.autoplay_context = Some(self.state_context_from_page(
                     page,
                     context.metadata,
                     context.restrictions.take(),
                     context.uri.as_deref(),
+                    None,
                     Some(Provider::Autoplay),
                 ))
             }
@@ -349,6 +345,7 @@ impl ConnectState {
         metadata: HashMap<String, String>,
         restrictions: Option<Restrictions>,
         new_context_uri: Option<&str>,
+        context_length: Option<usize>,
         provider: Option<Provider>,
     ) -> StateContext {
         let new_context_uri = new_context_uri.unwrap_or(self.context_uri());
@@ -356,10 +353,12 @@ impl ConnectState {
         let tracks = page
             .tracks
             .iter()
-            .flat_map(|track| {
+            .enumerate()
+            .flat_map(|(i, track)| {
                 match self.context_to_provided_track(
                     track,
                     Some(new_context_uri),
+                    context_length.map(|l| l + i),
                     Some(&page.metadata),
                     provider.clone(),
                 ) {
@@ -373,20 +372,28 @@ impl ConnectState {
             .collect::<Vec<_>>();
 
         StateContext {
-            tracks,
+            tracks: tracks.into(),
+            skip_track: None,
             restrictions,
             metadata,
             index: ContextIndex::new(),
         }
     }
 
+    pub fn is_skip_track(&self, track: &ProvidedTrack) -> bool {
+        self.get_context(self.active_context)
+            .ok()
+            .and_then(|t| t.skip_track.as_ref().map(|t| t.uri == track.uri))
+            .unwrap_or(false)
+    }
+
     pub fn merge_context(&mut self, context: Option<Context>) -> Option<()> {
         let mut context = context?;
         if matches!(context.uri, Some(ref uri) if uri != self.context_uri()) {
             return None;
         }
 
-        let current_context = self.context.as_mut()?;
+        let current_context = self.get_context_mut(ContextType::Default).ok()?;
         let new_page = context.pages.pop()?;
 
         for new_track in new_page.tracks {
@@ -421,12 +428,7 @@ impl ConnectState {
         ty: ContextType,
         new_index: usize,
     ) -> Result<(), StateError> {
-        let context = match ty {
-            ContextType::Default => self.context.as_mut(),
-            ContextType::Shuffle => self.shuffle_context.as_mut(),
-            ContextType::Autoplay => self.autoplay_context.as_mut(),
-        }
-        .ok_or(StateError::NoContext(ty))?;
+        let context = self.get_context_mut(ty)?;
 
         context.index.track = new_index as u32;
         Ok(())
@@ -436,6 +438,7 @@ impl ConnectState {
         &self,
         ctx_track: &ContextTrack,
         context_uri: Option<&str>,
+        context_index: Option<usize>,
         page_metadata: Option<&HashMap<String, String>>,
         provider: Option<Provider>,
     ) -> Result<ProvidedTrack, Error> {
@@ -479,19 +482,25 @@ impl ConnectState {
         };
 
         if let Some(context_uri) = context_uri {
-            track.set_context_uri(context_uri.to_string());
-            track.set_entity_uri(context_uri.to_string());
+            track.set_entity_uri(context_uri);
+            track.set_context_uri(context_uri);
+        }
+
+        if let Some(index) = context_index {
+            track.set_context_index(index);
         }
 
         if matches!(provider, Provider::Autoplay) {
-            track.set_autoplay(true)
+            track.set_from_autoplay(true)
         }
 
         Ok(track)
     }
 
     pub fn fill_context_from_page(&mut self, page: ContextPage) -> Result<(), Error> {
-        let context = self.state_context_from_page(page, HashMap::new(), None, None, None);
+        let ctx_len = self.context.as_ref().map(|c| c.tracks.len());
+        let context = self.state_context_from_page(page, HashMap::new(), None, None, ctx_len, None);
+
         let ctx = self
             .context
             .as_mut()
diff --git a/connect/src/state/handle.rs b/connect/src/state/handle.rs
index 1c1a4b325..659ed92cc 100644
--- a/connect/src/state/handle.rs
+++ b/connect/src/state/handle.rs
@@ -2,6 +2,7 @@ use crate::{
     core::{dealer::protocol::SetQueueCommand, Error},
     state::{
         context::{ContextType, ResetContext},
+        metadata::Metadata,
         ConnectState,
     },
 };
@@ -12,7 +13,7 @@ impl ConnectState {
         self.set_shuffle(shuffle);
 
         if shuffle {
-            return self.shuffle();
+            return self.shuffle(None);
         }
 
         self.reset_context(ResetContext::DefaultIndex);
@@ -21,11 +22,16 @@ impl ConnectState {
             return Ok(());
         }
 
-        let ctx = self.get_context(ContextType::Default)?;
-        let current_index =
-            ConnectState::find_index_in_context(ctx, |c| self.current_track(|t| c.uri == t.uri))?;
-
-        self.reset_playback_to_position(Some(current_index))
+        match self.current_track(|t| t.get_context_index()) {
+            Some(current_index) => self.reset_playback_to_position(Some(current_index)),
+            None => {
+                let ctx = self.get_context(ContextType::Default)?;
+                let current_index = ConnectState::find_index_in_context(ctx, |c| {
+                    self.current_track(|t| c.uri == t.uri)
+                })?;
+                self.reset_playback_to_position(Some(current_index))
+            }
+        }
     }
 
     pub fn handle_set_queue(&mut self, set_queue: SetQueueCommand) {
diff --git a/connect/src/state/metadata.rs b/connect/src/state/metadata.rs
index b1effb688..763244b7d 100644
--- a/connect/src/state/metadata.rs
+++ b/connect/src/state/metadata.rs
@@ -1,84 +1,82 @@
-use librespot_protocol::{context_track::ContextTrack, player::ProvidedTrack};
+use crate::{
+    protocol::{context::Context, context_track::ContextTrack, player::ProvidedTrack},
+    state::context::StateContext,
+};
 use std::collections::HashMap;
+use std::fmt::Display;
 
 const CONTEXT_URI: &str = "context_uri";
 const ENTITY_URI: &str = "entity_uri";
 const IS_QUEUED: &str = "is_queued";
 const IS_AUTOPLAY: &str = "autoplay.is_autoplay";
-
 const HIDDEN: &str = "hidden";
 const ITERATION: &str = "iteration";
 
+const CUSTOM_CONTEXT_INDEX: &str = "context_index";
+const CUSTOM_SHUFFLE_SEED: &str = "shuffle_seed";
+
+macro_rules! metadata_entry {
+    ( $get:ident, $set:ident, $clear:ident ($key:ident: $entry:ident)) => {
+        metadata_entry!( $get use get, $set, $clear ($key: $entry) -> Option<&String> );
+    };
+    ( $get_key:ident use $get:ident, $set:ident, $clear:ident ($key:ident: $entry:ident) -> $ty:ty ) => {
+        fn $get_key (&self) -> $ty {
+            self.$get($entry)
+        }
+
+        fn $set (&mut self, $key: impl Display) {
+            self.metadata_mut().insert($entry.to_string(), $key.to_string());
+        }
+
+        fn $clear(&mut self) {
+            self.metadata_mut().remove($entry);
+        }
+    };
+}
+
 #[allow(dead_code)]
 pub trait Metadata {
     fn metadata(&self) -> &HashMap<String, String>;
     fn metadata_mut(&mut self) -> &mut HashMap<String, String>;
 
-    fn is_from_queue(&self) -> bool {
-        matches!(self.metadata().get(IS_QUEUED), Some(is_queued) if is_queued.eq("true"))
-    }
-
-    fn is_from_autoplay(&self) -> bool {
-        matches!(self.metadata().get(IS_AUTOPLAY), Some(is_autoplay) if is_autoplay.eq("true"))
-    }
-
-    fn is_hidden(&self) -> bool {
-        matches!(self.metadata().get(HIDDEN), Some(is_hidden) if is_hidden.eq("true"))
-    }
-
-    fn get_context_uri(&self) -> Option<&String> {
-        self.metadata().get(CONTEXT_URI)
-    }
-
-    fn get_iteration(&self) -> Option<&String> {
-        self.metadata().get(ITERATION)
-    }
-
-    fn set_queued(&mut self, queued: bool) {
-        self.metadata_mut()
-            .insert(IS_QUEUED.to_string(), queued.to_string());
-    }
-
-    fn set_autoplay(&mut self, autoplay: bool) {
-        self.metadata_mut()
-            .insert(IS_AUTOPLAY.to_string(), autoplay.to_string());
+    fn get_bool(&self, entry: &str) -> bool {
+        matches!(self.metadata().get(entry), Some(entry) if entry.eq("true"))
     }
 
-    fn set_hidden(&mut self, hidden: bool) {
-        self.metadata_mut()
-            .insert(HIDDEN.to_string(), hidden.to_string());
+    fn get_usize(&self, entry: &str) -> Option<usize> {
+        self.metadata().get(entry)?.parse().ok()
     }
 
-    fn set_context_uri(&mut self, uri: String) {
-        self.metadata_mut().insert(CONTEXT_URI.to_string(), uri);
+    fn get(&self, entry: &str) -> Option<&String> {
+        self.metadata().get(entry)
     }
 
-    fn set_entity_uri(&mut self, uri: String) {
-        self.metadata_mut().insert(ENTITY_URI.to_string(), uri);
-    }
+    metadata_entry!(is_from_queue use get_bool, set_from_queue, remove_from_queue (is_queued: IS_QUEUED) -> bool);
+    metadata_entry!(is_from_autoplay use get_bool, set_from_autoplay, remove_from_autoplay (is_autoplay: IS_AUTOPLAY) -> bool);
+    metadata_entry!(is_hidden use get_bool, set_hidden, remove_hidden (is_hidden: HIDDEN) -> bool);
 
-    fn add_iteration(&mut self, iter: i64) {
-        self.metadata_mut()
-            .insert(ITERATION.to_string(), iter.to_string());
-    }
+    metadata_entry!(get_context_index use get_usize, set_context_index, remove_context_index (context_index: CUSTOM_CONTEXT_INDEX) -> Option<usize>);
+    metadata_entry!(get_context_uri, set_context_uri, remove_context_uri (context_uri: CONTEXT_URI));
+    metadata_entry!(get_entity_uri, set_entity_uri, remove_entity_uri (entity_uri: ENTITY_URI));
+    metadata_entry!(get_iteration, set_iteration, remove_iteration (iteration: ITERATION));
+    metadata_entry!(get_shuffle_seed, set_shuffle_seed, remove_shuffle_seed (shuffle_seed: CUSTOM_SHUFFLE_SEED));
 }
 
-impl Metadata for ContextTrack {
-    fn metadata(&self) -> &HashMap<String, String> {
-        &self.metadata
-    }
-
-    fn metadata_mut(&mut self) -> &mut HashMap<String, String> {
-        &mut self.metadata
-    }
+macro_rules! impl_metadata {
+    ($impl_for:ident) => {
+        impl Metadata for $impl_for {
+            fn metadata(&self) -> &HashMap<String, String> {
+                &self.metadata
+            }
+
+            fn metadata_mut(&mut self) -> &mut HashMap<String, String> {
+                &mut self.metadata
+            }
+        }
+    };
 }
 
-impl Metadata for ProvidedTrack {
-    fn metadata(&self) -> &HashMap<String, String> {
-        &self.metadata
-    }
-
-    fn metadata_mut(&mut self) -> &mut HashMap<String, String> {
-        &mut self.metadata
-    }
-}
+impl_metadata!(ContextTrack);
+impl_metadata!(ProvidedTrack);
+impl_metadata!(Context);
+impl_metadata!(StateContext);
diff --git a/connect/src/state/options.rs b/connect/src/state/options.rs
index 97484f675..6f3848107 100644
--- a/connect/src/state/options.rs
+++ b/connect/src/state/options.rs
@@ -1,9 +1,14 @@
-use crate::state::context::ContextType;
-use crate::state::{ConnectState, StateError};
-use librespot_core::Error;
-use librespot_protocol::player::{ContextIndex, ContextPlayerOptions};
+use crate::{
+    core::Error,
+    protocol::player::ContextPlayerOptions,
+    state::{
+        context::{ContextType, ResetContext},
+        metadata::Metadata,
+        ConnectState, StateError,
+    },
+};
 use protobuf::MessageField;
-use rand::prelude::SliceRandom;
+use rand::Rng;
 
 impl ConnectState {
     fn add_options_if_empty(&mut self) {
@@ -39,7 +44,7 @@ impl ConnectState {
         self.set_repeat_context(false);
     }
 
-    pub fn shuffle(&mut self) -> Result<(), Error> {
+    pub fn shuffle(&mut self, seed: Option<u64>) -> Result<(), Error> {
         if let Some(reason) = self
             .player()
             .restrictions
@@ -55,22 +60,22 @@ impl ConnectState {
         self.clear_prev_track();
         self.clear_next_tracks();
 
-        let current_uri = self.current_track(|t| &t.uri);
+        let current_track = self.current_track(|t| t.clone().take());
 
-        let ctx = self.get_context(ContextType::Default)?;
-        let current_track = Self::find_index_in_context(ctx, |t| &t.uri == current_uri)?;
+        self.reset_context(ResetContext::DefaultIndex);
+        let ctx = self.get_context_mut(ContextType::Default)?;
 
-        let mut shuffle_context = ctx.clone();
         // we don't need to include the current track, because it is already being played
-        shuffle_context.tracks.remove(current_track);
+        ctx.skip_track = current_track;
 
-        let mut rng = rand::thread_rng();
-        shuffle_context.tracks.shuffle(&mut rng);
-        shuffle_context.index = ContextIndex::new();
+        let seed = seed
+            .unwrap_or_else(|| rand::thread_rng().gen_range(100_000_000_000..1_000_000_000_000));
 
-        self.shuffle_context = Some(shuffle_context);
-        self.set_active_context(ContextType::Shuffle);
-        self.fill_up_context = ContextType::Shuffle;
+        ctx.tracks.shuffle_with_seed(seed);
+        ctx.set_shuffle_seed(seed);
+
+        self.set_active_context(ContextType::Default);
+        self.fill_up_context = ContextType::Default;
         self.fill_up_next_tracks()?;
 
         Ok(())
diff --git a/connect/src/state/tracks.rs b/connect/src/state/tracks.rs
index 054685757..07d039912 100644
--- a/connect/src/state/tracks.rs
+++ b/connect/src/state/tracks.rs
@@ -23,7 +23,7 @@ impl<'ct> ConnectState {
             ..Default::default()
         };
         delimiter.set_hidden(true);
-        delimiter.add_iteration(iteration);
+        delimiter.set_iteration(iteration);
 
         delimiter
     }
@@ -124,6 +124,7 @@ impl<'ct> ConnectState {
                     continue;
                 }
                 Some(next) if next.is_unavailable() => continue,
+                Some(next) if self.is_skip_track(&next) => continue,
                 other => break other,
             };
         };
@@ -141,12 +142,10 @@ impl<'ct> ConnectState {
             self.set_active_context(ContextType::Autoplay);
             None
         } else {
-            let ctx = self.get_context(ContextType::Default)?;
-            let new_index = Self::find_index_in_context(ctx, |c| c.uri == new_track.uri);
-            match new_index {
-                Ok(new_index) => Some(new_index as u32),
-                Err(why) => {
-                    error!("didn't find the track in the current context: {why}");
+            match new_track.get_context_index() {
+                Some(new_index) => Some(new_index as u32),
+                None => {
+                    error!("the given context track had no set context_index");
                     None
                 }
             }
@@ -323,7 +322,7 @@ impl<'ct> ConnectState {
                     }
                 }
                 None => break,
-                Some(ct) if ct.is_unavailable() => {
+                Some(ct) if ct.is_unavailable() || self.is_skip_track(ct) => {
                     new_index += 1;
                     continue;
                 }
@@ -414,7 +413,7 @@ impl<'ct> ConnectState {
 
         track.set_provider(Provider::Queue);
         if !track.is_from_queue() {
-            track.set_queued(true);
+            track.set_from_queue(true);
         }
 
         let next_tracks = self.next_tracks_mut();
diff --git a/connect/src/state/transfer.rs b/connect/src/state/transfer.rs
index 7404bf550..1e2f40cf2 100644
--- a/connect/src/state/transfer.rs
+++ b/connect/src/state/transfer.rs
@@ -26,6 +26,7 @@ impl ConnectState {
             track,
             transfer.current_session.context.uri.as_deref(),
             None,
+            None,
             transfer
                 .queue
                 .is_playing_queue
@@ -52,10 +53,25 @@ impl ConnectState {
             _ => player.playback_speed = 1.,
         }
 
+        let mut shuffle_seed = None;
         if let Some(session) = transfer.current_session.as_mut() {
             player.play_origin = session.play_origin.take().map(Into::into).into();
             player.suppressions = session.suppressions.take().map(Into::into).into();
 
+            // maybe at some point we can use the shuffle seed provided by spotify,
+            // but I doubt it, as spotify doesn't use true randomness but rather an algorithm
+            // based shuffle
+            trace!(
+                "shuffle_seed: <{:?}> (spotify), <{:?}> (own)",
+                session.shuffle_seed,
+                session.context.get_shuffle_seed()
+            );
+
+            shuffle_seed = session
+                .context
+                .get_shuffle_seed()
+                .and_then(|seed| seed.parse().ok());
+
             if let Some(mut ctx) = session.context.take() {
                 player.restrictions = ctx.restrictions.take().map(Into::into).into();
                 for (key, value) in ctx.metadata {
@@ -73,6 +89,8 @@ impl ConnectState {
             }
         }
 
+        self.transfer_shuffle_seed = shuffle_seed;
+
         self.clear_prev_track();
         self.clear_next_tracks();
         self.update_queue_revision()
@@ -134,6 +152,7 @@ impl ConnectState {
                 track,
                 Some(self.context_uri()),
                 None,
+                None,
                 Some(Provider::Queue),
             ) {
                 self.add_to_queue(queued_track, false);
@@ -143,7 +162,9 @@ impl ConnectState {
         if self.shuffling_context() {
             self.set_current_track(current_index.unwrap_or_default())?;
             self.set_shuffle(true);
-            self.shuffle()?;
+
+            let previous_seed = self.transfer_shuffle_seed.take();
+            self.shuffle(previous_seed)?;
         } else {
             self.reset_playback_to_position(current_index)?;
         }