diff --git a/app/components/ReleasePlayer/EpisodeSelectorMenu.tsx b/app/components/ReleasePlayer/EpisodeSelectorMenu.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/app/components/ReleasePlayer/MediaPlayer.module.css b/app/components/ReleasePlayer/MediaPlayer.module.css
index 2e62f67..e772ab0 100644
--- a/app/components/ReleasePlayer/MediaPlayer.module.css
+++ b/app/components/ReleasePlayer/MediaPlayer.module.css
@@ -38,7 +38,7 @@
   position: absolute;
   height: calc(2 * var(--base));
   line-height: calc(2 * var(--base));
-  bottom: calc(3 * var(--base));
+  bottom: calc(1 * var(--base));
   left: var(--base);
   right: var(--base);
 }
@@ -110,6 +110,27 @@
   user-select: none;
 }
 
+.media-source-dialog {
+  --media-menu-icon-height: 20px;
+  --media-menu-item-icon-height: 20px;
+  --media-settings-menu-min-width: calc(10 * var(--base));
+  --media-menu-transform-in: translateY(0) scale(1);
+  --media-menu-transform-out: translateY(20px) rotate(3deg) scale(1);
+  background: rgb(30 30 30 / .8);
+  min-width: var(--media-settings-menu-min-width, 170px);
+  position: absolute;
+  right: 10px;
+  bottom: calc(3 * var(--base));
+  padding-block: calc(0.15 * var(--base));
+  padding-inline: calc(0.6 * var(--base));
+  margin-right: 10px;
+  margin-bottom: 17px;
+  border-radius: 8px;
+  user-select: none;
+  width: fit-content;
+  max-height: 50%;
+}
+
 .media-settings-menu[hidden] {
   display: block;
   visibility: visible;
diff --git a/app/components/ReleasePlayer/PlayerParsing.ts b/app/components/ReleasePlayer/PlayerParsing.ts
new file mode 100644
index 0000000..9c45221
--- /dev/null
+++ b/app/components/ReleasePlayer/PlayerParsing.ts
@@ -0,0 +1,177 @@
+import { tryCatchPlayer, tryCatchAPI } from "#/api/utils";
+
+export async function _fetchAPI(
+  url: string,
+  onErrorMsg: string,
+  setPlayerError: (state) => void,
+  onErrorCodes?: Record<number, string>
+) {
+  const { data, error } = await tryCatchAPI(fetch(url));
+  if (error) {
+    let errorDetail = "Мы правда не знаем что произошло...";
+
+    if (error.name) {
+      if (error.name == "TypeError") {
+        errorDetail = "Не удалось подключиться к серверу";
+      } else {
+        errorDetail = `Неизвестная ошибка ${error.name}: ${error.message}`;
+      }
+    }
+    if (error.code) {
+      if (Object.keys(onErrorCodes).includes(error.code.toString())) {
+        errorDetail = onErrorCodes[error.code.toString()];
+      } else {
+        errorDetail = `API вернуло ошибку: ${error.code}`;
+      }
+    }
+
+    setPlayerError({
+      message: onErrorMsg,
+      detail: errorDetail,
+    });
+    return null;
+  }
+  return data;
+}
+
+export async function _fetchPlayer(
+  url: string,
+  setPlayerError: (state) => void
+) {
+  const { data, error } = (await tryCatchPlayer(fetch(url))) as any;
+  if (error) {
+    let errorDetail = "Мы правда не знаем что произошло...";
+
+    if (error.name) {
+      if (error.name == "TypeError") {
+        errorDetail = "Не удалось подключиться к серверу";
+      } else {
+        errorDetail = `Неизвестная ошибка ${error.name}: ${error.message}`;
+      }
+    } else if (error.message) {
+      errorDetail = error.message;
+    }
+
+    setPlayerError({
+      message: "Не удалось получить ссылку на видео",
+      detail: errorDetail,
+    });
+    return null;
+  }
+  return data;
+}
+
+export const _fetchKodikManifest = async (
+  url: string,
+  setPlayerError: (state) => void
+) => {
+  // Fetch episode links via edge function
+  const data = await _fetchPlayer(
+    `https://anix-player.wah.su/?url=${url}&player=kodik`,
+    setPlayerError
+  );
+  if (data) {
+    let lowQualityLink = data.links["360"][0].src; // we assume that 360p is always present
+
+    if (!lowQualityLink.includes("//")) {
+      // check if link is encrypted, else do nothing
+      const decryptedBase64 = lowQualityLink.replace(/[a-zA-Z]/g, (e) => {
+        return String.fromCharCode(
+          (e <= "Z" ? 90 : 122) >= (e = e.charCodeAt(0) + 18) ? e : e - 26
+        );
+      });
+      lowQualityLink = atob(decryptedBase64);
+    }
+
+    if (lowQualityLink.includes("https://")) {
+      // string the https prefix, since we add it manually
+      lowQualityLink = lowQualityLink.replace("https://", "//");
+    }
+
+    let manifest = `https:${lowQualityLink.replace("360.mp4:hls:", "")}`;
+    let poster = `https:${lowQualityLink.replace("360.mp4:hls:manifest.m3u8", "thumb001.jpg")}`;
+
+    if (lowQualityLink.includes("animetvseries")) {
+      // if link includes "animetvseries" we need to construct manifest ourselves
+      let blobTxt = "#EXTM3U\n";
+
+      if (data.links.hasOwnProperty("240")) {
+        blobTxt += "#EXT-X-STREAM-INF:RESOLUTION=427x240,BANDWIDTH=200000\n";
+        !data.links["240"][0].src.startsWith("https:") ?
+          (blobTxt += `https:${data.links["240"][0].src}\n`)
+        : (blobTxt += `${data.links["240"][0].src}\n`);
+      }
+
+      if (data.links.hasOwnProperty("360")) {
+        blobTxt += "#EXT-X-STREAM-INF:RESOLUTION=578x360,BANDWIDTH=400000\n";
+        !data.links["360"][0].src.startsWith("https:") ?
+          (blobTxt += `https:${data.links["360"][0].src}\n`)
+        : (blobTxt += `${data.links["360"][0].src}\n`);
+      }
+
+      if (data.links.hasOwnProperty("480")) {
+        blobTxt += "#EXT-X-STREAM-INF:RESOLUTION=854x480,BANDWIDTH=596000\n";
+        !data.links["480"][0].src.startsWith("https:") ?
+          (blobTxt += `https:${data.links["480"][0].src}\n`)
+        : (blobTxt += `${data.links["480"][0].src}\n`);
+      }
+
+      if (data.links.hasOwnProperty("720")) {
+        blobTxt += "#EXT-X-STREAM-INF:RESOLUTION=1280x720,BANDWIDTH=1280000\n";
+        !data.links["720"][0].src.startsWith("https:") ?
+          (blobTxt += `https:${data.links["720"][0].src}\n`)
+        : (blobTxt += `${data.links["720"][0].src}\n`);
+      }
+
+      let file = new File([blobTxt], "manifest.m3u8", {
+        type: "application/x-mpegURL",
+      });
+      manifest = URL.createObjectURL(file);
+    }
+    return { manifest, poster };
+  }
+  return { manifest: null, poster: null };
+};
+
+export const _fetchAnilibriaManifest = async (
+  url: string,
+  setPlayerError: (state) => void
+) => {
+  const id = url.split("?id=")[1].split("&ep=")[0];
+  const epid = url.split("?id=")[1].split("&ep=")[1];
+  const data = await _fetchPlayer(
+    `https://api.anilibria.tv/v3/title?id=${id}`,
+    setPlayerError
+  );
+  if (data) {
+    const host = `https://${data.player.host}`;
+    const ep = data.player.list[epid];
+
+    // we need to manually construct a manifest file for a hls player
+    const blobTxt = `#EXTM3U\n${ep.hls.sd && `#EXT-X-STREAM-INF:RESOLUTION=854x480,BANDWIDTH=596000\n${host}${ep.hls.sd}\n`}${ep.hls.hd && `#EXT-X-STREAM-INF:RESOLUTION=1280x720,BANDWIDTH=1280000\n${host}${ep.hls.hd}\n`}${ep.hls.fhd && `#EXT-X-STREAM-INF:RESOLUTION=1920x1080,BANDWIDTH=2560000\n${host}${ep.hls.fhd}\n`}`;
+    let file = new File([blobTxt], "manifest.m3u8", {
+      type: "application/x-mpegURL",
+    });
+    let manifest = URL.createObjectURL(file);
+    let poster = `https://anixart.libria.fun${ep.preview}`;
+    return { manifest, poster };
+  }
+  return { manifest: null, poster: null };
+};
+
+export const _fetchSibnetManifest = async (
+  url: string,
+  setPlayerError: (state) => void
+) => {
+  // Fetch data via cloud endpoint
+  const data = await _fetchPlayer(
+    `https://sibnet.anix-player.wah.su/?url=${url}`,
+    setPlayerError
+  );
+  if (data) {
+    let manifest = data.video;
+    let poster = data.poster;
+    return { manifest, poster };
+  }
+  return { manifest: null, poster: null };
+};
diff --git a/app/components/ReleasePlayer/ReleasePlayerCustom.tsx b/app/components/ReleasePlayer/ReleasePlayerCustom.tsx
index e9d5ac7..5b04cbf 100644
--- a/app/components/ReleasePlayer/ReleasePlayerCustom.tsx
+++ b/app/components/ReleasePlayer/ReleasePlayerCustom.tsx
@@ -4,17 +4,17 @@ import { Button, Card } from "flowbite-react";
 import { useEffect, useState } from "react";
 import { ENDPOINTS } from "#/api/config";
 
-import { VoiceoverSelector } from "./VoiceoverSelector";
-import { SourceSelector } from "./SourceSelector";
-import { EpisodeSelector } from "./EpisodeSelector";
-import { Spinner } from "../Spinner/Spinner";
-import { useUserPlayerPreferencesStore } from "#/store/player";
+// import { VoiceoverSelector } from "./VoiceoverSelector";
+// import { SourceSelector } from "./SourceSelector";
+// import { EpisodeSelector } from "./EpisodeSelector";
+// import { Spinner } from "../Spinner/Spinner";
+// import { useUserPlayerPreferencesStore } from "#/store/player";
 
 import HlsVideo from "hls-video-element/react";
 import VideoJS from "videojs-video-element/react";
 // import MediaThemeSutro from "./MediaThemeSutro";
-import { getAnonEpisodesWatched } from "./ReleasePlayer";
-import { tryCatchPlayer, tryCatchAPI } from "#/api/utils";
+// import { getAnonEpisodesWatched } from "./ReleasePlayer";
+// import { tryCatchPlayer, tryCatchAPI } from "#/api/utils";
 
 import Styles from "./MediaPlayer.module.css";
 
@@ -32,6 +32,7 @@ import {
   MediaPreviewTimeDisplay,
   MediaPipButton,
   MediaAirplayButton,
+  MediaChromeDialog,
 } from "media-chrome/react";
 import {
   MediaPlaybackRateMenu,
@@ -40,6 +41,7 @@ import {
   MediaSettingsMenuButton,
   MediaSettingsMenuItem,
 } from "media-chrome/react/menu";
+import { VoiceoverSelectorMenu } from "./VoiceoverSelectorMenu";
 
 export const ReleasePlayerCustom = (props: {
   id: number;
@@ -63,334 +65,153 @@ export const ReleasePlayerCustom = (props: {
     type: null,
     useCustom: false,
   });
-  const [playbackRate, setPlaybackRate] = useState(1);
   const [playerError, setPlayerError] = useState(null);
-  const [isErrorDetailsOpen, setIsErrorDetailsOpen] = useState(false);
-  const [isLoading, setIsLoading] = useState(true);
+  // const [playbackRate, setPlaybackRate] = useState(1);
+  // const [isErrorDetailsOpen, setIsErrorDetailsOpen] = useState(false);
+  // const [isLoading, setIsLoading] = useState(true);
 
-  const playerPreferenceStore = useUserPlayerPreferencesStore();
-  const preferredVO = playerPreferenceStore.getPreferredVoiceover(props.id);
-  const preferredSource = playerPreferenceStore.getPreferredPlayer(props.id);
+  // const playerPreferenceStore = useUserPlayerPreferencesStore();
+  // const preferredVO = playerPreferenceStore.getPreferredVoiceover(props.id);
+  // const preferredSource = playerPreferenceStore.getPreferredPlayer(props.id);
 
-  async function _fetchAPI(
-    url: string,
-    onErrorMsg: string,
-    onErrorCodes?: Record<number, string>
-  ) {
-    const { data, error } = await tryCatchAPI(fetch(url));
-    if (error) {
-      let errorDetail = "Мы правда не знаем что произошло...";
+  // old info fetching
 
-      if (error.name) {
-        if (error.name == "TypeError") {
-          errorDetail = "Не удалось подключиться к серверу";
-        } else {
-          errorDetail = `Неизвестная ошибка ${error.name}: ${error.message}`;
-        }
-      }
-      if (error.code) {
-        if (Object.keys(onErrorCodes).includes(error.code.toString())) {
-          errorDetail = onErrorCodes[error.code.toString()];
-        } else {
-          errorDetail = `API вернуло ошибку: ${error.code}`;
-        }
-      }
+  // useEffect(() => {
+  //   const __getInfo = async () => {
+  //     let url = `${ENDPOINTS.release.episode}/${props.id}/${voiceover.selected.id}`;
+  //     const src = await _fetchAPI(
+  //       url,
+  //       "Не удалось получить информацию о источниках"
+  //     );
+  //     if (src) {
+  //       const selectedSrc =
+  //         src.sources.find((source: any) => source.name === preferredSource) ||
+  //         src.sources[0];
+  //       if (selectedSrc.episodes_count == 0) {
+  //         const remSources = src.sources.filter(
+  //           (source: any) => source.id !== selectedSrc.id
+  //         );
+  //         setSource({
+  //           selected: remSources[0],
+  //           available: remSources,
+  //         });
+  //         return;
+  //       }
+  //       setSource({
+  //         selected: selectedSrc,
+  //         available: src.sources,
+  //       });
+  //     }
+  //   };
+  //   if (voiceover.selected) {
+  //     __getInfo();
+  //   }
+  //   // eslint-disable-next-line react-hooks/exhaustive-deps
+  // }, [voiceover.selected]);
 
-      setPlayerError({
-        message: onErrorMsg,
-        detail: errorDetail,
-      });
-      return null;
-    }
-    return data;
-  }
+  // useEffect(() => {
+  //   const __getInfo = async () => {
+  //     let url = `${ENDPOINTS.release.episode}/${props.id}/${voiceover.selected.id}/${source.selected.id}`;
+  //     if (props.token) {
+  //       url += `?token=${props.token}`;
+  //     }
+  //     const episodes = await _fetchAPI(
+  //       url,
+  //       "Не удалось получить информацию о эпизодах"
+  //     );
+  //     if (episodes) {
+  //       let anonEpisodesWatched = getAnonEpisodesWatched(
+  //         props.id,
+  //         source.selected.id,
+  //         voiceover.selected.id
+  //       );
+  //       let lastEpisodeWatched = Math.max.apply(
+  //         0,
+  //         Object.keys(
+  //           anonEpisodesWatched[props.id][source.selected.id][
+  //             voiceover.selected.id
+  //           ]
+  //         )
+  //       );
+  //       let selectedEpisode =
+  //         episodes.episodes.find(
+  //           (episode: any) => episode.position == lastEpisodeWatched
+  //         ) || episodes.episodes[0];
 
-  async function _fetchPlayer(url: string) {
-    const { data, error } = (await tryCatchPlayer(fetch(url))) as any;
-    if (error) {
-      let errorDetail = "Мы правда не знаем что произошло...";
+  //       setEpisode({
+  //         selected: selectedEpisode,
+  //         available: episodes.episodes,
+  //       });
+  //     }
+  //   };
+  //   if (source.selected) {
+  //     __getInfo();
+  //   }
+  //   // eslint-disable-next-line react-hooks/exhaustive-deps
+  // }, [source.selected]);
 
-      if (error.name) {
-        if (error.name == "TypeError") {
-          errorDetail = "Не удалось подключиться к серверу";
-        } else {
-          errorDetail = `Неизвестная ошибка ${error.name}: ${error.message}`;
-        }
-      } else if (error.message) {
-        errorDetail = error.message;
-      }
-
-      setPlayerError({
-        message: "Не удалось получить ссылку на видео",
-        detail: errorDetail,
-      });
-      return null;
-    }
-    return data;
-  }
-
-  const _fetchKodikManifest = async (url: string) => {
-    // Fetch data through a proxy
-    const data = await _fetchPlayer(
-      `https://anix-player.wah.su/?url=${url}&player=kodik`
-    );
-    if (data) {
-      let lowQualityLink = data.links["360"][0].src; // we assume that 360p is always present
-
-      if (!lowQualityLink.includes("//")) {
-        // check if link is encrypted, else do nothing
-        const decryptedBase64 = lowQualityLink.replace(/[a-zA-Z]/g, (e) => {
-          return String.fromCharCode(
-            (e <= "Z" ? 90 : 122) >= (e = e.charCodeAt(0) + 18) ? e : e - 26
-          );
-        });
-        lowQualityLink = atob(decryptedBase64);
-      }
-
-      if (lowQualityLink.includes("https://")) {
-        // string the https prefix, since we add it manually
-        lowQualityLink = lowQualityLink.replace("https://", "//");
-      }
-
-      let manifest = `https:${lowQualityLink.replace("360.mp4:hls:", "")}`;
-      let poster = `https:${lowQualityLink.replace("360.mp4:hls:manifest.m3u8", "thumb001.jpg")}`;
-
-      if (lowQualityLink.includes("animetvseries")) {
-        // if link includes "animetvseries" we need to construct manifest ourselves
-        let blobTxt = "#EXTM3U\n";
-
-        if (data.links.hasOwnProperty("240")) {
-          blobTxt += "#EXT-X-STREAM-INF:RESOLUTION=427x240,BANDWIDTH=200000\n";
-          !data.links["240"][0].src.startsWith("https:") ?
-            (blobTxt += `https:${data.links["240"][0].src}\n`)
-          : (blobTxt += `${data.links["240"][0].src}\n`);
-        }
-
-        if (data.links.hasOwnProperty("360")) {
-          blobTxt += "#EXT-X-STREAM-INF:RESOLUTION=578x360,BANDWIDTH=400000\n";
-          !data.links["360"][0].src.startsWith("https:") ?
-            (blobTxt += `https:${data.links["360"][0].src}\n`)
-          : (blobTxt += `${data.links["360"][0].src}\n`);
-        }
-
-        if (data.links.hasOwnProperty("480")) {
-          blobTxt += "#EXT-X-STREAM-INF:RESOLUTION=854x480,BANDWIDTH=596000\n";
-          !data.links["480"][0].src.startsWith("https:") ?
-            (blobTxt += `https:${data.links["480"][0].src}\n`)
-          : (blobTxt += `${data.links["480"][0].src}\n`);
-        }
-
-        if (data.links.hasOwnProperty("720")) {
-          blobTxt +=
-            "#EXT-X-STREAM-INF:RESOLUTION=1280x720,BANDWIDTH=1280000\n";
-          !data.links["720"][0].src.startsWith("https:") ?
-            (blobTxt += `https:${data.links["720"][0].src}\n`)
-          : (blobTxt += `${data.links["720"][0].src}\n`);
-        }
-
-        let file = new File([blobTxt], "manifest.m3u8", {
-          type: "application/x-mpegURL",
-        });
-        manifest = URL.createObjectURL(file);
-      }
-      return { manifest, poster };
-    }
-    return { manifest: null, poster: null };
-  };
-
-  const _fetchAnilibriaManifest = async (url: string) => {
-    const id = url.split("?id=")[1].split("&ep=")[0];
-    const data = await _fetchPlayer(
-      `https://api.anilibria.tv/v3/title?id=${id}`
-    );
-    if (data) {
-      const host = `https://${data.player.host}`;
-      const ep = data.player.list[episode.selected.position];
-
-      const blobTxt = `#EXTM3U\n${ep.hls.sd && `#EXT-X-STREAM-INF:RESOLUTION=854x480,BANDWIDTH=596000\n${host}${ep.hls.sd}\n`}${ep.hls.hd && `#EXT-X-STREAM-INF:RESOLUTION=1280x720,BANDWIDTH=1280000\n${host}${ep.hls.hd}\n`}${ep.hls.fhd && `#EXT-X-STREAM-INF:RESOLUTION=1920x1080,BANDWIDTH=2560000\n${host}${ep.hls.fhd}\n`}`;
-      let file = new File([blobTxt], "manifest.m3u8", {
-        type: "application/x-mpegURL",
-      });
-      let manifest = URL.createObjectURL(file);
-      let poster = `https://anixart.libria.fun${ep.preview}`;
-      return { manifest, poster };
-    }
-    return { manifest: null, poster: null };
-  };
-
-  const _fetchSibnetManifest = async (url: string) => {
-    const data = await _fetchPlayer(
-      `https://sibnet.anix-player.wah.su/?url=${url}`
-    );
-    if (data) {
-      let manifest = data.video;
-      let poster = data.poster;
-      return { manifest, poster };
-    }
-    return { manifest: null, poster: null };
-  };
-
-  useEffect(() => {
-    const __getInfo = async () => {
-      let url = `${ENDPOINTS.release.episode}/${props.id}`;
-      if (props.token) {
-        url += `?token=${props.token}`;
-      }
-      const vo = await _fetchAPI(
-        url,
-        "Не удалось получить информацию о озвучках",
-        { 1: "Просмотр запрещён" }
-      );
-      if (vo) {
-        const selectedVO =
-          vo.types.find((voiceover: any) => voiceover.name === preferredVO) ||
-          vo.types[0];
-        setVoiceover({
-          selected: selectedVO,
-          available: vo.types,
-        });
-      }
-    };
-    __getInfo();
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [props.id, props.token]);
-
-  useEffect(() => {
-    const __getInfo = async () => {
-      let url = `${ENDPOINTS.release.episode}/${props.id}/${voiceover.selected.id}`;
-      const src = await _fetchAPI(
-        url,
-        "Не удалось получить информацию о источниках"
-      );
-      if (src) {
-        const selectedSrc =
-          src.sources.find((source: any) => source.name === preferredSource) ||
-          src.sources[0];
-        if (selectedSrc.episodes_count == 0) {
-          const remSources = src.sources.filter(
-            (source: any) => source.id !== selectedSrc.id
-          );
-          setSource({
-            selected: remSources[0],
-            available: remSources,
-          });
-          return;
-        }
-        setSource({
-          selected: selectedSrc,
-          available: src.sources,
-        });
-      }
-    };
-    if (voiceover.selected) {
-      __getInfo();
-    }
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [voiceover.selected]);
-
-  useEffect(() => {
-    const __getInfo = async () => {
-      let url = `${ENDPOINTS.release.episode}/${props.id}/${voiceover.selected.id}/${source.selected.id}`;
-      if (props.token) {
-        url += `?token=${props.token}`;
-      }
-      const episodes = await _fetchAPI(
-        url,
-        "Не удалось получить информацию о эпизодах"
-      );
-      if (episodes) {
-        let anonEpisodesWatched = getAnonEpisodesWatched(
-          props.id,
-          source.selected.id,
-          voiceover.selected.id
-        );
-        let lastEpisodeWatched = Math.max.apply(
-          0,
-          Object.keys(
-            anonEpisodesWatched[props.id][source.selected.id][
-              voiceover.selected.id
-            ]
-          )
-        );
-        let selectedEpisode =
-          episodes.episodes.find(
-            (episode: any) => episode.position == lastEpisodeWatched
-          ) || episodes.episodes[0];
-
-        setEpisode({
-          selected: selectedEpisode,
-          available: episodes.episodes,
-        });
-      }
-    };
-    if (source.selected) {
-      __getInfo();
-    }
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [source.selected]);
-
-  useEffect(() => {
-    const __getInfo = async () => {
-      if (source.selected.name == "Kodik") {
-        const { manifest, poster } = await _fetchKodikManifest(
-          episode.selected.url
-        );
-        if (manifest) {
-          SetPlayerProps({
-            src: manifest,
-            poster: poster,
-            useCustom: true,
-            type: "hls",
-          });
-          setIsLoading(false);
-        }
-        return;
-      }
-      if (source.selected.name == "Libria") {
-        const { manifest, poster } = await _fetchAnilibriaManifest(
-          episode.selected.url
-        );
-        if (manifest) {
-          SetPlayerProps({
-            src: manifest,
-            poster: poster,
-            useCustom: true,
-            type: "hls",
-          });
-          setIsLoading(false);
-        }
-        return;
-      }
-      if (source.selected.name == "Sibnet") {
-        const { manifest, poster } = await _fetchSibnetManifest(
-          episode.selected.url
-        );
-        if (manifest) {
-          SetPlayerProps({
-            src: manifest,
-            poster: poster,
-            useCustom: true,
-            type: "mp4",
-          });
-          setIsLoading(false);
-        }
-        return;
-      }
-      SetPlayerProps({
-        src: episode.selected.url,
-        poster: null,
-        useCustom: false,
-        type: null,
-      });
-      setIsLoading(false);
-    };
-    if (episode.selected) {
-      setIsLoading(true);
-      setPlayerError(null);
-      __getInfo();
-    }
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [episode.selected]);
+  // useEffect(() => {
+  //   const __getInfo = async () => {
+  //     if (source.selected.name == "Kodik") {
+  //       const { manifest, poster } = await _fetchKodikManifest(
+  //         episode.selected.url
+  //       );
+  //       if (manifest) {
+  //         SetPlayerProps({
+  //           src: manifest,
+  //           poster: poster,
+  //           useCustom: true,
+  //           type: "hls",
+  //         });
+  //         setIsLoading(false);
+  //       }
+  //       return;
+  //     }
+  //     if (source.selected.name == "Libria") {
+  //       const { manifest, poster } = await _fetchAnilibriaManifest(
+  //         episode.selected.url
+  //       );
+  //       if (manifest) {
+  //         SetPlayerProps({
+  //           src: manifest,
+  //           poster: poster,
+  //           useCustom: true,
+  //           type: "hls",
+  //         });
+  //         setIsLoading(false);
+  //       }
+  //       return;
+  //     }
+  //     if (source.selected.name == "Sibnet") {
+  //       const { manifest, poster } = await _fetchSibnetManifest(
+  //         episode.selected.url
+  //       );
+  //       if (manifest) {
+  //         SetPlayerProps({
+  //           src: manifest,
+  //           poster: poster,
+  //           useCustom: true,
+  //           type: "mp4",
+  //         });
+  //         setIsLoading(false);
+  //       }
+  //       return;
+  //     }
+  //     SetPlayerProps({
+  //       src: episode.selected.url,
+  //       poster: null,
+  //       useCustom: false,
+  //       type: null,
+  //     });
+  //     setIsLoading(false);
+  //   };
+  //   if (episode.selected) {
+  //     setIsLoading(true);
+  //     setPlayerError(null);
+  //     __getInfo();
+  //   }
+  //   // eslint-disable-next-line react-hooks/exhaustive-deps
+  // }, [episode.selected]);
 
   return (
     <Card className="">
@@ -472,7 +293,7 @@ export const ReleasePlayerCustom = (props: {
           defaultStreamType="on-demand"
           className={`relative w-full overflow-hidden ${Styles["media-controller"]}`}
         >
-          <div className="absolute flex flex-wrap w-full gap-2 top-2 left-2">
+          {/* <div className="absolute flex flex-wrap w-full gap-2 top-2 left-2">
             {voiceover.selected && (
               <VoiceoverSelector
                 availableVoiceover={voiceover.available}
@@ -489,21 +310,21 @@ export const ReleasePlayerCustom = (props: {
                 release_id={props.id}
               />
             )}
-          </div>
-
+          </div> */}
           <HlsVideo
             className="object-contain h-full aspect-video"
             slot="media"
             src={playerProps.src}
             poster={playerProps.poster}
-            defaultPlaybackRate={playbackRate}
-            onRateChange={(e) => {
-              // @ts-ignore
-              setPlaybackRate(e.target.playbackRate || 1);
-            }}
+            // defaultPlaybackRate={playbackRate}
+            // onRateChange={(e) => {
+            //   // @ts-ignore
+            //   setPlaybackRate(e.target.playbackRate || 1);
+            // }}
           />
           <div className={`${Styles["media-gradient-bottom"]}`}></div>
           <MediaSettingsMenu
+            id="settings"
             hidden
             anchor="auto"
             className={`${Styles["media-settings-menu"]}`}
@@ -530,6 +351,21 @@ export const ReleasePlayerCustom = (props: {
               </MediaRenditionMenu>
             </MediaSettingsMenuItem>
           </MediaSettingsMenu>
+          <MediaChromeDialog
+            id="source"
+            className={`${Styles["media-source-dialog"]}`}
+          >
+            <div className="flex gap-2 overflow-hidden">
+              <VoiceoverSelectorMenu
+                release_id={props.id}
+                token={props.token}
+                voiceover={voiceover.selected}
+                voiceoverList={voiceover.available}
+                setVoiceover={setVoiceover}
+                setPlayerError={setPlayerError}
+              />
+            </div>
+          </MediaChromeDialog>
           <MediaControlBar className={`${Styles["media-control-bar"]}`}>
             <MediaPlayButton
               mediaPaused={true}
@@ -644,6 +480,9 @@ export const ReleasePlayerCustom = (props: {
                 />
               </svg>
             </MediaSeekForwardButton>
+            <MediaSettingsMenuButton
+              {...({ invoketarget: "source" } as any)}
+            ></MediaSettingsMenuButton>
             <MediaSettingsMenuButton
               className={`${Styles["media-button"]} ${Styles["media-settings-menu-button"]}`}
             >
@@ -690,17 +529,17 @@ export const ReleasePlayerCustom = (props: {
                 className={`${Styles["svg"]}`}
               >
                 <path
-                  stroke-linecap="round"
-                  stroke-linejoin="round"
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
                   d="M20.5 20h1.722c.43 0 .778-.32.778-.714v-8.572c0-.394-.348-.714-.778-.714H9.778c-.43 0-.778.32-.778.714v1.429"
                 />
                 <path
-                  stroke-linecap="round"
-                  stroke-linejoin="round"
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
                   d="M11.5 20H9.778c-.43 0-.778-.32-.778-.714v-8.572c0-.394.348-.714.778-.714h12.444c.43 0 .778.32.778.714v1.429"
                 />
                 <path
-                  stroke-linejoin="round"
+                  strokeLinejoin="round"
                   d="m16 19 3.464 3.75h-6.928L16 19Z"
                 />
               </svg>
@@ -780,7 +619,7 @@ export const ReleasePlayerCustom = (props: {
               </svg>
             </MediaFullscreenButton>
           </MediaControlBar>
-          {episode.selected && source.selected && voiceover.selected && (
+          {/* {episode.selected && source.selected && voiceover.selected && (
             <div className="w-full">
               <EpisodeSelector
                 availableEpisodes={episode.available}
@@ -792,11 +631,9 @@ export const ReleasePlayerCustom = (props: {
                 token={props.token}
               />
             </div>
-          )}
+          )} */}
         </MediaController>
       </div>
-
-      <div></div>
     </Card>
   );
 };
diff --git a/app/components/ReleasePlayer/VoiceoverSelectorMenu.tsx b/app/components/ReleasePlayer/VoiceoverSelectorMenu.tsx
new file mode 100644
index 0000000..de208e7
--- /dev/null
+++ b/app/components/ReleasePlayer/VoiceoverSelectorMenu.tsx
@@ -0,0 +1,105 @@
+"use client";
+
+import { ENDPOINTS } from "#/api/config";
+import { useEffect } from "react";
+import { _fetchAPI } from "./PlayerParsing";
+import { useUserPlayerPreferencesStore } from "#/store/player";
+import { Button } from "flowbite-react";
+import { numberDeclension } from "#/api/utils";
+
+interface Voiceover {
+  id: number;
+  name: string;
+  icon: string;
+  episodes_count: number;
+  view_count: number;
+  pinned: boolean;
+}
+
+interface VoiceoverSelectorMenuProps {
+  release_id: number;
+  token: string | null;
+  setVoiceover: (state) => void;
+  voiceover: Voiceover;
+  voiceoverList: Voiceover[];
+  setPlayerError: (state) => void;
+}
+
+export const VoiceoverSelectorMenu = ({
+  release_id,
+  token,
+  setVoiceover,
+  voiceover,
+  voiceoverList,
+  setPlayerError,
+}: VoiceoverSelectorMenuProps) => {
+  const playerPreferenceStore = useUserPlayerPreferencesStore();
+  const preferredVO = playerPreferenceStore.getPreferredVoiceover(release_id);
+
+  useEffect(() => {
+    const __getInfo = async () => {
+      let url = `${ENDPOINTS.release.episode}/${release_id}`;
+      if (token) {
+        url += `?token=${token}`;
+      }
+      const vo = await _fetchAPI(
+        url,
+        "Не удалось получить информацию о озвучках",
+        setPlayerError,
+        { 1: "Просмотр запрещён" }
+      );
+      if (vo) {
+        const selectedVO =
+          vo.types.find((voiceover: any) => voiceover.name === preferredVO) ||
+          vo.types[0];
+        setVoiceover({
+          selected: selectedVO,
+          available: vo.types,
+        });
+      }
+    };
+    __getInfo();
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [release_id, token]);
+
+  return (
+    <div className="max-h-full flex flex-col gap-4 items-start justify-start overflow-x-hidden overflow-y-auto px-2 py-2 scrollbar-thin scrollbar-thumb-[rgb(60_60_60_/_.8)] scrollbar-track-[rgb(30_30_30_/_.8)]">
+      {voiceoverList && voiceoverList.length > 0 ?
+        voiceoverList.map((vo: Voiceover) => {
+          return (
+            <Button key={`release-${release_id}-voiceover-${vo.id}`}
+                className={`h-fit ${voiceover.id == vo.id ? "text-white" : "text-gray-500 hover:text-gray-300"} transition-colors`}
+                onClick={() => {
+                        setVoiceover({
+                            selected: vo,
+                            available: voiceoverList
+                        });
+                        playerPreferenceStore.setPreferredVoiceover(
+                            release_id,
+                            vo.name
+                        );
+                    }
+                }
+            >
+                <div className="flex flex-col gap-1">
+                    <div className="flex gap-2">
+                        {/* eslint-disable-next-line @next/next/no-img-element */}
+                        {vo.icon ? <img alt="" className="w-6 h-6 rounded-full" src={vo.icon}></img> : ""}
+                        <span>{vo.name}</span>
+                        {vo.pinned && (
+                            <span className={`h-4 iconify material-symbols--push-pin ${voiceover.id == vo.id ? "bg-white" : "bg-gray-500 hover:bg-gray-300"} transition-colors`}></span>
+                        )}
+                    </div>
+                    <div className="flex gap-2">
+                        {/* eslint-disable-next-line @next/next/no-img-element */}
+                        <span>{vo.episodes_count} {numberDeclension(vo.episodes_count, "серия", "серии", "серий")}</span>
+                        <span>{vo.view_count} {numberDeclension(vo.view_count, "просмотр", "просмотра", "просмотров")}</span>
+                    </div>
+                </div>
+            </Button>
+          );
+        })
+      : ""}
+    </div>
+  );
+};
diff --git a/package-lock.json b/package-lock.json
index 0af315f..0758888 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -41,6 +41,7 @@
         "eslint-config-next": "14.2.5",
         "eslint-plugin-react-refresh": "^0.4.19",
         "postcss": "^8",
+        "tailwind-scrollbar": "^3.1.0",
         "tailwindcss": "^3.4.1"
       }
     },
@@ -6664,6 +6665,19 @@
         "url": "https://github.com/sponsors/dcastil"
       }
     },
+    "node_modules/tailwind-scrollbar": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-3.1.0.tgz",
+      "integrity": "sha512-pmrtDIZeHyu2idTejfV59SbaJyvp1VRjYxAjZBH0jnyrPRo6HL1kD5Glz8VPagasqr6oAx6M05+Tuw429Z8jxg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.13.0"
+      },
+      "peerDependencies": {
+        "tailwindcss": "3.x"
+      }
+    },
     "node_modules/tailwindcss": {
       "version": "3.4.17",
       "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
diff --git a/package.json b/package.json
index 35d5b6e..3b6982f 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
     "eslint-config-next": "14.2.5",
     "eslint-plugin-react-refresh": "^0.4.19",
     "postcss": "^8",
+    "tailwind-scrollbar": "^3.1.0",
     "tailwindcss": "^3.4.1"
   }
 }
diff --git a/tailwind.config.js b/tailwind.config.js
index 1864db8..d3ac4d9 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -15,7 +15,8 @@ module.exports = {
   ],
   plugins: [
     addIconSelectors(["mdi", "material-symbols", "twemoji", "fa6-brands"]),
-    flowbiteReact
+    flowbiteReact,
+    require("tailwind-scrollbar")
   ],
   darkMode: "selector",
   theme: {