diff --git a/src/plugins/mediaPlaybackSpeed/README.md b/src/plugins/mediaPlaybackSpeed/README.md
new file mode 100644
index 000000000..5e8387544
--- /dev/null
+++ b/src/plugins/mediaPlaybackSpeed/README.md
@@ -0,0 +1,5 @@
+# MediaPlaybackSpeed
+
+Allows changing the (default) playback speed of media embeds
+
+![New icon with menu to change the playback speed](https://github.com/Vendicated/Vencord/assets/24937357/21792b09-8d6a-45be-a6e8-916cdd67a477)
diff --git a/src/plugins/mediaPlaybackSpeed/components/SpeedIcon.tsx b/src/plugins/mediaPlaybackSpeed/components/SpeedIcon.tsx
new file mode 100644
index 000000000..df93f1332
--- /dev/null
+++ b/src/plugins/mediaPlaybackSpeed/components/SpeedIcon.tsx
@@ -0,0 +1,19 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+export default function SpeedIcon() {
+ return (
+
+ );
+}
diff --git a/src/plugins/mediaPlaybackSpeed/index.tsx b/src/plugins/mediaPlaybackSpeed/index.tsx
new file mode 100644
index 000000000..e1776a933
--- /dev/null
+++ b/src/plugins/mediaPlaybackSpeed/index.tsx
@@ -0,0 +1,151 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import "./styles.css";
+
+import { definePluginSettings } from "@api/Settings";
+import { classNameFactory } from "@api/Styles";
+import ErrorBoundary from "@components/ErrorBoundary";
+import { makeRange } from "@components/PluginSettings/components";
+import { Devs } from "@utils/constants";
+import definePlugin, { OptionType } from "@utils/types";
+import { ContextMenuApi, FluxDispatcher, Heading, Menu, React, Tooltip, useEffect } from "@webpack/common";
+import { RefObject } from "react";
+
+import SpeedIcon from "./components/SpeedIcon";
+
+const cl = classNameFactory("vc-media-playback-speed-");
+
+const min = 0.25;
+const max = 3.5;
+const speeds = makeRange(min, max, 0.25);
+
+const settings = definePluginSettings({
+ test: {
+ type: OptionType.COMPONENT,
+ description: "",
+ component() {
+ return
+ Default playback speeds
+ ;
+ }
+ },
+ defaultVoiceMessageSpeed: {
+ type: OptionType.SLIDER,
+ default: 1,
+ description: "Voice messages",
+ markers: speeds,
+ },
+ defaultVideoSpeed: {
+ type: OptionType.SLIDER,
+ default: 1,
+ description: "Videos",
+ markers: speeds,
+ },
+ defaultAudioSpeed: {
+ type: OptionType.SLIDER,
+ default: 1,
+ description: "Audios",
+ markers: speeds,
+ },
+});
+
+type MediaRef = RefObject | undefined;
+
+export default definePlugin({
+ name: "MediaPlaybackSpeed",
+ description: "Allows changing the (default) playback speed of media embeds",
+ authors: [Devs.D3SOX],
+
+ settings,
+
+ PlaybackSpeedComponent({ mediaRef }: { mediaRef: MediaRef }) {
+ const changeSpeed = (speed: number) => {
+ const media = mediaRef?.current;
+ if (media) {
+ media.playbackRate = speed;
+ }
+ };
+
+ useEffect(() => {
+ if (!mediaRef?.current) return;
+ const media = mediaRef.current;
+ if (media.tagName === "AUDIO") {
+ const isVoiceMessage = media.className.includes("audioElement_");
+ changeSpeed(isVoiceMessage ? settings.store.defaultVoiceMessageSpeed : settings.store.defaultAudioSpeed);
+ } else if (media.tagName === "VIDEO") {
+ changeSpeed(settings.store.defaultVideoSpeed);
+ }
+ }, [mediaRef]);
+
+ return (
+
+ {tooltipProps => (
+
+ )}
+
+ );
+ },
+
+ renderComponent(mediaRef: MediaRef) {
+ return
+
+ ;
+ },
+
+ patches: [
+ // voice message embeds
+ {
+ find: "\"--:--\"",
+ replacement: {
+ match: /onVolumeShow:\i,onVolumeHide:\i\}\)(?<=useCallback\(\(\)=>\{let \i=(\i).current;.+?)/,
+ replace: "$&,$self.renderComponent($1)"
+ }
+ },
+ // audio & video embeds
+ {
+ // need to pass media ref via props to make it easily accessible from inside controls
+ find: "renderControls(){",
+ replacement: {
+ match: /onToggleMuted:this.toggleMuted,/,
+ replace: "$&mediaRef:this.mediaRef,"
+ }
+ },
+ {
+ find: "AUDIO:\"AUDIO\"",
+ replacement: {
+ match: /onVolumeHide:\i,iconClassName:\i.controlIcon,iconColor:"currentColor",sliderWrapperClassName:\i.volumeSliderWrapper\}\)\}\),/,
+ replace: "$&$self.renderComponent(this.props.mediaRef),"
+ }
+ }
+ ]
+});
diff --git a/src/plugins/mediaPlaybackSpeed/styles.css b/src/plugins/mediaPlaybackSpeed/styles.css
new file mode 100644
index 000000000..816190a48
--- /dev/null
+++ b/src/plugins/mediaPlaybackSpeed/styles.css
@@ -0,0 +1,10 @@
+.vc-media-playback-speed-icon {
+ background-color: transparent;
+ height: 100%;
+ z-index: 2;
+ color: var(--interactive-normal);
+}
+
+.vc-media-playback-speed-icon:hover {
+ color: var(--interactive-active);
+}