diff --git a/src/Lib/AbstractMediaContext.hh b/src/Lib/AbstractMediaContext.hh
index bc896bfcc193203a6f1732eb93116c34c1e1178a..1267b175146f8bac7889e3e687e10f7fe59ce15c 100644
--- a/src/Lib/AbstractMediaContext.hh
+++ b/src/Lib/AbstractMediaContext.hh
@@ -82,7 +82,7 @@ protected:
     }
 
 public:
-    virtual ~AbstractMediaStream() noexcept = default;
+    virtual ~AbstractMediaStream() noexcept {}
     int getStreamIndex() const noexcept { return streamIndexInContext; }
     QString getName() const noexcept { return QString::fromUtf8(codec->name); }
     AVCodecID getCodecId() const noexcept { return codecId; }
diff --git a/src/Lib/Video.cc b/src/Lib/Video.cc
index d6fe6e57eda1ae7d358d8d4be4a5ef2ccbd99fd5..390b82a7b44626c218d7875c34bd9b103f874d26 100644
--- a/src/Lib/Video.cc
+++ b/src/Lib/Video.cc
@@ -6,45 +6,31 @@
 
 using namespace Vivy;
 
-VideoContext::VideoContext(const QString &path)
-    : filePath(path)
+VideoStream::VideoStream(AVCodec *streamCodec, AVFormatContext *formatArg, AVStream *streamArg,
+                         int index)
+    : Super(streamCodec, formatArg, streamArg, index)
 {
-    if (!format)
-        throw std::runtime_error("out of memory, can't create allocate the AVFormatContext");
-
-    const std::string stdFilename = filePath.toStdString();
-    const char *filename          = stdFilename.c_str();
-    AVFormatContext *formatPtr    = format.get();
-
-    // Get info from video file
-    if (avformat_open_input(&formatPtr, filename, nullptr, nullptr) != 0) {
-        [[maybe_unused]] void *relatedOnFailure = format.release(); // freed by avformat_open_input
-        throw std::runtime_error("failed to open file");
-    }
+}
 
-    if (avformat_find_stream_info(formatPtr, nullptr) < 0) {
-        throw std::runtime_error("failed to get video stream info");
-    }
+QJsonObject
+VideoStream::getProperties() const noexcept
+{
+    return Super::getProperties();
+}
 
-    // Populate all the stream indexes
-    for (uint i = 0; i < format->nb_streams; ++i) {
-        AVStream *itFormat        = format->streams[i];
-        AVCodecParameters *params = itFormat->codecpar;
-        AVCodec *streamCodec      = avcodec_find_decoder(params->codec_id);
-        if (streamCodec && streamCodec->type == AVMEDIA_TYPE_VIDEO) {
-            videoStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFormat, i));
-        }
-    }
+VideoContext::VideoContext(const QString &path)
+    : Super(path)
+{
+}
 
-    // Get the default stream
-    defaultStreamIndex = av_find_best_stream(formatPtr, AVMEDIA_TYPE_VIDEO,
-                                             -1, // Let AV find one stream
-                                             -1, // We don't want related streams
-                                             nullptr, 0);
-    if (defaultStreamIndex < 0) {
-        qCritical() << "Could not find the best video stream";
-    }
+QJsonDocument
+VideoContext::getProperties() const noexcept
+{
+    return Super::getProperties();
+}
 
-    qDebug() << "Opened video context for" << path << "with duration" << formatPtr->duration
-             << "and default stream index" << defaultStreamIndex;
+QString
+VideoContext::getElementName() const noexcept
+{
+    return "Video" + Super::getElementName();
 }
diff --git a/src/Lib/Video.hh b/src/Lib/Video.hh
index 8dbb47580865610cb94b14c54c59ad1fdfaefe0d..e0826f0c3c191234e594b33f990b4de7b8360dff 100644
--- a/src/Lib/Video.hh
+++ b/src/Lib/Video.hh
@@ -21,68 +21,38 @@ extern "C" {
 
 namespace Vivy
 {
-class VideoContext;
-
-// Like an audio context, but for videos.
-class VideoContext final {
-    VIVY_UNMOVABLE_OBJECT(VideoContext)
+// Hold all the data for a video stream. Should only be owned by the parent
+// VideoContext instance.
+class VideoStream final : public AbstractMediaStream<AVMEDIA_TYPE_VIDEO> {
+    VIVY_UNMOVABLE_OBJECT(VideoStream)
+    using Super = AbstractMediaStream<AVMEDIA_TYPE_VIDEO>;
 
 public:
-    // Hold all the data for a video stream. Should only be owned by the parent
-    // VideoContext instance.
-    class Stream final {
-        VIVY_UNMOVABLE_OBJECT(Stream)
-
-    public:
-        Stream(AVCodec *, AVFormatContext *, AVStream *, int index);
-        ~Stream() noexcept;
-
-        size_t getWidth() const noexcept;
-        size_t getHeight() const noexcept;
-        size_t getDuration() const noexcept;
-        size_t getFramesPerSecond() const noexcept;
+    VideoStream(AVCodec *, AVFormatContext *, AVStream *, int index);
+    ~VideoStream() noexcept override = default;
 
-        QJsonObject getProperties() const noexcept;
+    size_t getWidth() const noexcept;
+    size_t getHeight() const noexcept;
+    size_t getDuration() const noexcept;
+    size_t getFramesPerSecond() const noexcept;
 
-        static inline Utils::DeleterFunctionType<AVCodecContext> codecContexteleter =
-            std::bind_front(Utils::freePPtrIfNotNull<AVCodecContext>, avcodec_free_context);
+    QJsonObject getProperties() const noexcept override;
 
-        using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>;
+    static inline Utils::DeleterFunctionType<AVCodecContext> codecContexteleter =
+        std::bind_front(Utils::freePPtrIfNotNull<AVCodecContext>, avcodec_free_context);
 
-    private:
-        AVCodecID codecId{ AV_CODEC_ID_NONE };
-        AVCodec *codec{ nullptr };
-        AVCodecParameters *codecParams{ nullptr };
-        AVCodecContextPtr codecContext{ nullptr };
-
-        AVStream *videoStream{ nullptr };
-
-        int streamIndexInVideoContext;
-    };
+private:
+};
 
-    using StreamPtr     = std::shared_ptr<Stream>;
-    using StreamWeakPtr = std::weak_ptr<Stream>;
+// Like an audio context, but for videos.
+class VideoContext final : public AbstractMediaContext<VideoStream> {
+    VIVY_UNMOVABLE_OBJECT(VideoContext)
+    using Super = AbstractMediaContext<VideoStream>;
 
 public:
     VideoContext(const QString &path);
 
-    StreamWeakPtr getStream(int) const noexcept;
-    StreamWeakPtr getDefaultStream() const noexcept;
-
-    QString getElementName() const noexcept;
-    QJsonDocument getProperties() const noexcept;
-
-private:
-    static inline Utils::DeleterFunctionType<AVFormatContext> avFormatContextDeleter =
-        std::bind_front(Utils::freePtrIfNotNull<AVFormatContext>, avformat_free_context);
-    using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(avFormatContextDeleter)>;
-    AVFormatContextPtr format{ avformat_alloc_context(), avFormatContextDeleter };
-
-    const QString filePath;
-    QMap<uint, StreamPtr> videoStreams{};
-
-    int defaultStreamIndex{ -1 };
-
-    StreamPtr spareNullSreamPtr{ nullptr };
-};
+    QString getElementName() const noexcept override;
+    QJsonDocument getProperties() const noexcept override;
 };
+}