diff --git a/Resources/iOS/Images.xcassets/NewPlayer/pip.enter.imageset/Contents.json b/Resources/iOS/Images.xcassets/NewPlayer/pip.enter.imageset/Contents.json
new file mode 100644
index 0000000000000000000000000000000000000000..6fab6174b995d35f5b67554223d3e8a825632dcf
--- /dev/null
+++ b/Resources/iOS/Images.xcassets/NewPlayer/pip.enter.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "filename" : "pip.enter.24x24.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "pip.enter.24x24@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "pip.enter.24x24@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/Resources/iOS/Images.xcassets/NewPlayer/pip.enter.imageset/pip.enter.24x24.png b/Resources/iOS/Images.xcassets/NewPlayer/pip.enter.imageset/pip.enter.24x24.png
new file mode 100644
index 0000000000000000000000000000000000000000..9be881d5ef21709c9a7618790055792bb7e7338f
Binary files /dev/null and b/Resources/iOS/Images.xcassets/NewPlayer/pip.enter.imageset/pip.enter.24x24.png differ
diff --git a/Resources/iOS/Images.xcassets/NewPlayer/pip.enter.imageset/pip.enter.24x24@2x.png b/Resources/iOS/Images.xcassets/NewPlayer/pip.enter.imageset/pip.enter.24x24@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..b0f011328ea45f444bbe1e1321a9140efe88368b
Binary files /dev/null and b/Resources/iOS/Images.xcassets/NewPlayer/pip.enter.imageset/pip.enter.24x24@2x.png differ
diff --git a/Resources/iOS/Images.xcassets/NewPlayer/pip.enter.imageset/pip.enter.24x24@3x.png b/Resources/iOS/Images.xcassets/NewPlayer/pip.enter.imageset/pip.enter.24x24@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..c93663fb47704e1b849aa25906fd642200c01d93
Binary files /dev/null and b/Resources/iOS/Images.xcassets/NewPlayer/pip.enter.imageset/pip.enter.24x24@3x.png differ
diff --git a/Sources/Playback/Control/PictureInPictureMediaController.swift b/Sources/Playback/Control/PictureInPictureMediaController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..b13e7ddffc2e5d95f39077533d7aac995997bb47
--- /dev/null
+++ b/Sources/Playback/Control/PictureInPictureMediaController.swift
@@ -0,0 +1,53 @@
+/*****************************************************************************
+ * PictureInPictureMediaController.swift
+ * VLC for iOS
+ *****************************************************************************
+ * Copyright (c) 2025 VLC authors and VideoLAN
+ * $Id$
+ *
+ * Authors: Maxime Chapelet <umxprime # videolabs.io>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+import Foundation
+import VLCKit
+
+@objc(VLCPictureInPictureMediaController)
+final class PictureInPictureMediaController: NSObject {
+    private let mediaPlayer: VLCMediaPlayer
+    @objc(initWithMediaPlayer:)
+    init(_ mediaPlayer: VLCMediaPlayer) {
+        self.mediaPlayer = mediaPlayer
+    }
+}
+
+extension PictureInPictureMediaController: VLCPictureInPictureMediaControlling {
+    func play() {
+        mediaPlayer.play()
+    }
+
+    func pause() {
+        mediaPlayer.pause()
+    }
+
+    func seek(by offset: Int64, completion: (() -> Void)!) {
+        mediaPlayer.jump(withOffset: Int32(offset), completion: completion)
+    }
+
+    func mediaLength() -> Int64 {
+        return mediaPlayer.media?.length.value?.int64Value ?? 0
+    }
+
+    func mediaTime() -> Int64 {
+        return mediaPlayer.time.value?.int64Value ?? 0
+    }
+
+    func isMediaSeekable() -> Bool {
+        return mediaPlayer.isSeekable
+    }
+
+    func isMediaPlaying() -> Bool {
+        return mediaPlayer.isPlaying
+    }
+}
diff --git a/Sources/Playback/Control/VLCPlaybackService.h b/Sources/Playback/Control/VLCPlaybackService.h
index 3e7cb019ab3b5e060e710bb90cef7050d03c5e75..f4fc636e239b371f07baacacc3e9add11e39de8c 100644
--- a/Sources/Playback/Control/VLCPlaybackService.h
+++ b/Sources/Playback/Control/VLCPlaybackService.h
@@ -168,6 +168,7 @@ NS_SWIFT_NAME(PlaybackService)
 - (void)addSubtitlesToCurrentPlaybackFromURL:(NSURL *)subtitleURL;
 
 - (void)setAmplification:(CGFloat)amplification forBand:(unsigned int)index;
+- (void)togglePictureInPicture;
 
 #if TARGET_OS_IOS
 - (void)savePlaybackState;
diff --git a/Sources/Playback/Control/VLCPlaybackService.m b/Sources/Playback/Control/VLCPlaybackService.m
index 4941fd6303209936ea673ebef6a517b6e9e89459..362b6de28562fbb6de4037e99c51ca517bd7f99e 100644
--- a/Sources/Playback/Control/VLCPlaybackService.m
+++ b/Sources/Playback/Control/VLCPlaybackService.m
@@ -2,7 +2,7 @@
  * VLCPlaybackService.m
  * VLC for iOS
  *****************************************************************************
- * Copyright (c) 2013-2023 VLC authors and VideoLAN
+ * Copyright (c) 2013-2025 VLC authors and VideoLAN
  * $Id$
  *
  * Authors: Felix Paul Kühne <fkuehne # videolan.org>
@@ -44,9 +44,9 @@ NSString *const VLCPlaybackServiceShuffleModeUpdated = @"VLCPlaybackServiceShuff
 NSString *const VLCPlaybackServicePlaybackDidMoveOnToNextItem = @"VLCPlaybackServicePlaybackDidMoveOnToNextItem";
 
 #if TARGET_OS_IOS
-@interface VLCPlaybackService () <VLCMediaPlayerDelegate, VLCMediaDelegate, VLCMediaListPlayerDelegate, EqualizerViewDelegate>
+@interface VLCPlaybackService () <VLCMediaPlayerDelegate, VLCMediaDelegate, VLCMediaListPlayerDelegate, EqualizerViewDelegate, VLCDrawable, VLCPictureInPictureDrawable>
 #else
-@interface VLCPlaybackService () <VLCMediaPlayerDelegate, VLCMediaDelegate, VLCMediaListPlayerDelegate>
+@interface VLCPlaybackService () <VLCMediaPlayerDelegate, VLCMediaDelegate, VLCMediaListPlayerDelegate, VLCDrawable, VLCPictureInPictureDrawable>
 #endif
 {
     VLCMediaPlayer *_backgroundDummyPlayer;
@@ -86,6 +86,9 @@ NSString *const VLCPlaybackServicePlaybackDidMoveOnToNextItem = @"VLCPlaybackSer
     BOOL _openInMiniPlayer;
 }
 
+@property (weak, atomic) id<VLCPictureInPictureWindowControlling> pipController;
+@property (atomic) id<VLCPictureInPictureMediaControlling> mediaController;
+
 @end
 
 @implementation VLCPlaybackService
@@ -251,9 +254,9 @@ NSString *const VLCPlaybackServicePlaybackDidMoveOnToNextItem = @"VLCPlaybackSer
     }
     if (libVLCOptions.count > 0) {
         _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:libVLCOptions
-                                                      andDrawable:_actualVideoOutputView];
+                                                      andDrawable:self];
     } else {
-        _listPlayer = [[VLCMediaListPlayer alloc] initWithDrawable:_actualVideoOutputView];
+        _listPlayer = [[VLCMediaListPlayer alloc] initWithDrawable:self];
     }
     _listPlayer.delegate = self;
 
@@ -298,6 +301,9 @@ NSString *const VLCPlaybackServicePlaybackDidMoveOnToNextItem = @"VLCPlaybackSer
     newFilter.enabled = _adjustFilter.mediaPlayerAdjustFilter.isEnabled;
     _adjustFilter = [[VLCPlaybackServiceAdjustFilter alloc] initWithMediaPlayerAdjustFilter:newFilter];
     _mediaPlayer = _listPlayer.mediaPlayer;
+#if TARGET_OS_IOS
+    _mediaController = [[VLCPictureInPictureMediaController alloc] initWithMediaPlayer:_mediaPlayer];
+#endif
 
     [_mediaPlayer setDelegate:self];
     CGFloat defaultPlaybackSpeed = [[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue];
@@ -827,6 +833,10 @@ NSString *const VLCPlaybackServicePlaybackDidMoveOnToNextItem = @"VLCPlaybackSer
 
 - (void)mediaPlayerStateChanged:(VLCMediaPlayerState)currentState
 {
+    id<VLCPictureInPictureWindowControlling> pipController = _pipController;
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [pipController invalidatePlaybackState];
+    });
     switch (currentState) {
         case VLCMediaPlayerStateBuffering: {
             /* attach delegate */
@@ -1830,4 +1840,27 @@ NSString *const VLCPlaybackServicePlaybackDidMoveOnToNextItem = @"VLCPlaybackSer
                                                         object:self];
 }
 
+#pragma mark - VLCDrawable
+
+- (void)addSubview:(UIView *)view {
+    [_actualVideoOutputView addSubview:view];
+}
+
+- (CGRect)bounds { 
+    return [_actualVideoOutputView bounds];
+}
+
+#pragma mark - VLCPictureInPictureDrawable
+
+- (void (^)(id<VLCPictureInPictureWindowControlling>))pictureInPictureReady {
+    __weak typeof(self) drawable = self;
+    return ^(id<VLCPictureInPictureWindowControlling> pipController){
+        drawable.pipController = pipController;
+    };
+}
+
+- (void)togglePictureInPicture {
+    [self.pipController startPictureInPicture];
+}
+
 @end
diff --git a/Sources/Playback/Player/VideoPlayer-iOS/Subviews/MediaNavigationBar.swift b/Sources/Playback/Player/VideoPlayer-iOS/Subviews/MediaNavigationBar.swift
index b3ee0ded4bc4acebbb375ac305a22b8f8d1edea9..8962a0fb97f3087f8f062ea9fac3347875c7dbdb 100644
--- a/Sources/Playback/Player/VideoPlayer-iOS/Subviews/MediaNavigationBar.swift
+++ b/Sources/Playback/Player/VideoPlayer-iOS/Subviews/MediaNavigationBar.swift
@@ -16,6 +16,7 @@ import MediaPlayer
 @objc (VLCMediaNavigationBarDelegate)
 protocol MediaNavigationBarDelegate {
     func mediaNavigationBarDidTapClose(_ mediaNavigationBar: MediaNavigationBar)
+    @objc optional func mediaNavigationBarDidTapPictureInPicture(_ mediaNavigationBar: MediaNavigationBar)
     @objc optional func mediaNavigationBarDidToggleQueueView(_ mediaNavigationBar: MediaNavigationBar)
     @objc optional func mediaNavigationBarDidToggleChromeCast(_ mediaNavigationBar: MediaNavigationBar)
     func mediaNavigationBarDidCloseLongPress(_ mediaNavigationBar: MediaNavigationBar)
@@ -104,6 +105,16 @@ private enum RendererActionSheetContent: Int, CaseIterable {
         return chromeButton
     }()
 
+    lazy var pictureInPictureButton: UIButton = {
+        var button = UIButton(type: .system)
+        button.addTarget(self, action: #selector(togglePictureInPicture),
+                               for: .touchDown)
+        button.setImage(UIImage(named: "pip.enter"), for: .normal)
+        button.tintColor = .white
+        button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+        return button
+    }()
+
     private var closureQueue: (() -> Void)? = nil
 
     private lazy var deviceActionSheet: ActionSheet = {
@@ -187,6 +198,7 @@ private enum RendererActionSheetContent: Int, CaseIterable {
         addArrangedSubview(rotateButton)
         addArrangedSubview(queueButton)
         addArrangedSubview(deviceButton)
+        addArrangedSubview(pictureInPictureButton)
     }
 
     // MARK: Gesture recognizer
@@ -210,6 +222,10 @@ private enum RendererActionSheetContent: Int, CaseIterable {
                                           animated: true)
     }
 
+    func togglePictureInPicture() {
+        delegate?.mediaNavigationBarDidTapPictureInPicture?(self)
+    }
+
     func handleCloseTap() {
         assert(delegate != nil, "Delegate not set for MediaNavigationBar")
         delegate?.mediaNavigationBarDidTapClose(self)
diff --git a/Sources/Playback/Player/VideoPlayer-iOS/VideoPlayerViewController.swift b/Sources/Playback/Player/VideoPlayer-iOS/VideoPlayerViewController.swift
index 1388c3a091bffc715ebd1fe5246f6e068883c905..25e428b44f910559cfdc7507fed46f5a428661e5 100644
--- a/Sources/Playback/Player/VideoPlayer-iOS/VideoPlayerViewController.swift
+++ b/Sources/Playback/Player/VideoPlayer-iOS/VideoPlayerViewController.swift
@@ -1453,6 +1453,10 @@ extension VideoPlayerViewController {
     func mediaNavigationBarDisplayCloseAlert(_ mediaNavigationBar: MediaNavigationBar) {
         statusLabel.showStatusMessage(NSLocalizedString("MINIMIZE_HINT", comment: ""))
     }
+
+    func mediaNavigationBarDidTapPictureInPicture(_ mediaNavigationBar: MediaNavigationBar) {
+        playbackService.togglePictureInPicture()
+    }
 }
 
 // MARK: - MediaScrubProgressBarDelegate
diff --git a/VLC.xcodeproj/project.pbxproj b/VLC.xcodeproj/project.pbxproj
index 0eb5a9afbd23628566a232ede42499d6b6c8ecb8..0d48f6bccf907fa4ffbc7b7aecc9c564c3364509 100644
--- a/VLC.xcodeproj/project.pbxproj
+++ b/VLC.xcodeproj/project.pbxproj
@@ -104,6 +104,7 @@
 		597B403F2625E85000C0D81E /* SliderInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597B403E2625E85000C0D81E /* SliderInfoView.swift */; };
 		6C5B0C9E27A43098005AE25B /* PlaybackServiceAdjustFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5B0C9C27A43098005AE25B /* PlaybackServiceAdjustFilter.swift */; };
 		6C5B0C9F27A46258005AE25B /* PlaybackServiceAdjustFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5B0C9C27A43098005AE25B /* PlaybackServiceAdjustFilter.swift */; };
+		6CC3F6B32D230AEF00C15E33 /* PictureInPictureMediaController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC3F6B22D230AEF00C15E33 /* PictureInPictureMediaController.swift */; };
 		6D0B038825E7CBF90013DEF4 /* PopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D0B038725E7CBF90013DEF4 /* PopupView.swift */; };
 		6D3C676C23CDF1FC0039ACFD /* public in Resources */ = {isa = PBXBuildFile; fileRef = 6D3C676B23CDF1FC0039ACFD /* public */; };
 		6D4756B123607D4A005F670E /* EditActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4756B023607D49005F670E /* EditActions.swift */; };
@@ -688,6 +689,7 @@
 		57087A12E77ACEB9D1D30E33 /* Pods-VLC-tvOS.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VLC-tvOS.distribution.xcconfig"; path = "Target Support Files/Pods-VLC-tvOS/Pods-VLC-tvOS.distribution.xcconfig"; sourceTree = "<group>"; };
 		597B403E2625E85000C0D81E /* SliderInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderInfoView.swift; sourceTree = "<group>"; };
 		6C5B0C9C27A43098005AE25B /* PlaybackServiceAdjustFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PlaybackServiceAdjustFilter.swift; path = Sources/Playback/Control/PlaybackServiceAdjustFilter.swift; sourceTree = SOURCE_ROOT; };
+		6CC3F6B22D230AEF00C15E33 /* PictureInPictureMediaController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PictureInPictureMediaController.swift; sourceTree = "<group>"; };
 		6D0B038725E7CBF90013DEF4 /* PopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupView.swift; sourceTree = "<group>"; };
 		6D3C676B23CDF1FC0039ACFD /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
 		6D4756B023607D49005F670E /* EditActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditActions.swift; sourceTree = "<group>"; };
@@ -1738,6 +1740,7 @@
 				418DFE9E211C93C6005D3652 /* CustomDialogRendererHandler.swift */,
 				6C5B0C9C27A43098005AE25B /* PlaybackServiceAdjustFilter.swift */,
 				8D43712C2056AF1600F36458 /* VLCRendererDiscovererManager.swift */,
+				6CC3F6B22D230AEF00C15E33 /* PictureInPictureMediaController.swift */,
 			);
 			path = Control;
 			sourceTree = "<group>";
@@ -3859,6 +3862,7 @@
 				91C1BB8025EFD7A40096F97E /* ColorThemeExtension.swift in Sources */,
 				7D3784C2183A9938009EE944 /* VLCSlider.m in Sources */,
 				7D3784C3183A9938009EE944 /* VLCStatusLabel.m in Sources */,
+				6CC3F6B32D230AEF00C15E33 /* PictureInPictureMediaController.swift in Sources */,
 				4144156C20ECE6330078EC37 /* FileServerView.swift in Sources */,
 				40C95A07256E929D002DD208 /* PlaybackSpeedView.swift in Sources */,
 				416DACB720B6DB9A001BC75D /* PlayingExternallyView.swift in Sources */,