diff --git a/src/Lib/Audio.cc b/src/Lib/Audio.cc
index 23ad3e86e0fe014206f8e41bb7f0c044c7a12aa6..47684383ae37da01c3aff1deb430b71e7b7f4b39 100644
--- a/src/Lib/Audio.cc
+++ b/src/Lib/Audio.cc
@@ -9,125 +9,29 @@ using namespace Vivy;
 
 // Create an audio contecxt from a file
 AudioContext::AudioContext(const QString &path)
-    : filePath(path)
+    : Super(path)
 {
-    if (!format)
-        throw std::runtime_error("out of memory, can't create allocate the AVFormatContext");
-
-    const std::string filePathStdHolder = filePath.toStdString();
-    const char *filename                = filePathStdHolder.c_str();
-    AVFormatContext *formatPtr          = format.get();
-
-    // Get the format from the audio 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 audio stream info");
-    }
-
-    // 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_AUDIO) {
-            audioStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFormat, i));
-        }
-    }
-
-    // Get the default stream
-    defaultStreamIndex = av_find_best_stream(formatPtr, AVMEDIA_TYPE_AUDIO,
-                                             -1, // Let AV find one stream
-                                             -1, // We don't want related streams
-                                             nullptr, 0);
-    if (defaultStreamIndex < 0) {
-        qCritical() << "Could not find the best audio stream";
-    }
-
-    qDebug() << "Opened audio context for" << path << "with duration" << formatPtr->duration
-             << "and default stream index" << defaultStreamIndex;
-}
-
-// Get a specific audio stream, try to lock the weak pointer. if the index was
-// not present the used_count will be 0 and you won't be able to lock it.
-AudioContext::StreamWeakPtr
-AudioContext::getStream(int index) const noexcept
-{
-    if (index < 0)
-        return StreamWeakPtr{ spareNullSreamPtr };
-
-    uint unsignedIndex = static_cast<uint>(index);
-    const auto found   = audioStreams.find(unsignedIndex);
-
-    if (found != audioStreams.end())
-        return StreamWeakPtr{ *found };
-
-    return StreamWeakPtr{ spareNullSreamPtr };
-}
-
-// Get the default stream of this audio context. If no default audio stream is
-// present you won't be able to lock the weak pointer.
-AudioContext::StreamWeakPtr
-AudioContext::getDefaultStream() const noexcept
-{
-    if (defaultStreamIndex < 0)
-        return StreamWeakPtr{ spareNullSreamPtr };
-    return getStream(defaultStreamIndex);
 }
 
 QString
 AudioContext::getElementName() const noexcept
 {
-    QFileInfo file(filePath);
-    return "AudioContext: " + file.baseName();
+    return "Audio" + Super::getElementName();
 }
 
 QJsonDocument
 AudioContext::getProperties() const noexcept
 {
-    QJsonDocument ret;
-    QJsonArray streams;
-
-    QFileInfo file(filePath);
-
-    for (const auto &audioStreamPtr : audioStreams) {
-        streams.append(audioStreamPtr->getProperties());
-    }
-
-    QJsonObject self{ { "Streams", streams }, { "Base name", file.baseName() } };
-
-    ret.setObject(self);
-    return ret;
+    return Super::getProperties();
 }
 
-// AudioContext::Stream class implementation
+// AudioStream class implementation
 
 // Constructor, need an AVFormat and an AVStream
-AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *formatPtr, AVStream *stream,
-                             int index)
-    : codecId(stream->codecpar->codec_id)
-    , codec(streamCodec)
-    , codecParams(stream->codecpar)
-    , audioStream(stream)
-    , streamIndexInAudioContext(index)
-    , dataFormat(formatPtr)
+AudioStream::AudioStream(AVCodec *streamCodec, AVFormatContext *formatPtr, AVStream *streamArg,
+                         int index)
+    : Super(streamCodec, formatPtr, streamArg, index)
 {
-    if (codec == nullptr)
-        throw std::runtime_error("failed to find a decoder for stream");
-
-    codecContext.reset(avcodec_alloc_context3(codec));
-    if (!codecContext)
-        throw std::runtime_error("failed to allocate codec context");
-
-    if (avcodec_parameters_to_context(codecContext.get(), codecParams) < 0)
-        throw std::runtime_error("failed to copy parameters to codec context");
-
-    if (avcodec_open2(codecContext.get(), codec, nullptr) < 0)
-        throw std::runtime_error("failed to open audio decoder for a stream");
-
     SwrContext *s = dataSwrContext.get();
     av_opt_set_int(s, "in_channel_count", codecContext->channels, 0);
     av_opt_set_int(s, "out_channel_count", 1, 0);
@@ -140,14 +44,9 @@ AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *formatPtr, A
     swr_init(s);
     if (!swr_is_initialized(s))
         throw std::runtime_error("failed to initialize SwrContext resampler");
-
-    qDebug() << "[Stream] Codec" << codec->name << "id:" << codecId;
-    qDebug() << "[Stream] sample rate:" << codecParams->sample_rate;
-    qDebug() << "[Stream] bit rate:   " << codecParams->bit_rate;
-    qDebug() << "[Stream] channels:   " << codecParams->channels;
 }
 
-AudioContext::Stream::~Stream() noexcept
+AudioStream::~AudioStream() noexcept
 {
     if (dataPtr) {
         qDebug() << "Free data ptr";
@@ -157,10 +56,9 @@ AudioContext::Stream::~Stream() noexcept
 }
 
 QJsonObject
-AudioContext::Stream::getProperties() const noexcept
+AudioStream::getProperties() const noexcept
 {
-    QJsonObject ret;
-    ret.insert("Codec name", codec->name);
+    QJsonObject ret = Super::getProperties();
     ret.insert("Sample rate", codecParams->sample_rate);
     ret.insert("Bit rate", static_cast<int>(codecParams->bit_rate));
     ret.insert("Channels", codecParams->channels);
@@ -169,11 +67,11 @@ AudioContext::Stream::getProperties() const noexcept
 
 // Decode the data, don't call it if data already decoded
 void
-AudioContext::Stream::decodeData()
+AudioStream::decodeData()
 {
     if (isDecoded())
         throw std::logic_error("audio stream is already resampled");
-    qDebug() << "Launch decoding of stream" << streamIndexInAudioContext;
+    qDebug() << "Launch decoding of stream" << streamIndexInContext;
 
     AVPacket packet;
     av_init_packet(&packet);
@@ -181,7 +79,7 @@ AudioContext::Stream::decodeData()
     // Iterate through frames
     while (av_read_frame(dataFormat, &packet) >= 0) {
         // Only decode audio
-        if (packet.stream_index != streamIndexInAudioContext) {
+        if (packet.stream_index != streamIndexInContext) {
             av_packet_unref(&packet);
             continue;
         }
@@ -242,13 +140,13 @@ AudioContext::Stream::decodeData()
         av_packet_unref(&packet);
     }
 
-    qDebug() << "Decoding data finished for stream" << streamIndexInAudioContext
+    qDebug() << "Decoding data finished for stream" << streamIndexInContext
              << "dataPtr =" << dataPtr << "with dataSize =" << dataSize;
 }
 
 // Delete decoded data, clean up thing
 void
-AudioContext::Stream::cleanUpData() noexcept
+AudioStream::cleanUpData() noexcept
 {
     free(dataPtr);
     dataPtr  = nullptr;
@@ -257,14 +155,14 @@ AudioContext::Stream::cleanUpData() noexcept
 
 // Get the number of channels in the audio context stream
 int
-AudioContext::Stream::getChannels() const noexcept
+AudioStream::getChannels() const noexcept
 {
     return codecContext->channels;
 }
 
 // Get the sample rate
 int
-AudioContext::Stream::getSampleRate() const noexcept
+AudioStream::getSampleRate() const noexcept
 {
     return codecContext->sample_rate;
 }
@@ -272,35 +170,21 @@ AudioContext::Stream::getSampleRate() const noexcept
 // Get the sample rate of the decoded data (need to call decodeData for that
 // value to have a meaning).
 int
-AudioContext::Stream::getDataSampleRate() const noexcept
+AudioStream::getDataSampleRate() const noexcept
 {
     return resamplerSampleRate;
 }
 
-// Get the name of the codec used to decode the stream
-QString
-AudioContext::Stream::getName() const noexcept
-{
-    return QString::fromUtf8(codec->name);
-}
-
-// Get the codec's ID used to decode the stream
-AVCodecID
-AudioContext::Stream::getCodecId() const noexcept
-{
-    return codecId;
-}
-
 // Get the bit rate of the stream
 qint64
-AudioContext::Stream::getBitRate() const noexcept
+AudioStream::getBitRate() const noexcept
 {
     return codecContext->bit_rate;
 }
 
 // Get the information about the decoded state of this stream
 bool
-AudioContext::Stream::isDecoded() const noexcept
+AudioStream::isDecoded() const noexcept
 {
     return dataPtr != nullptr;
 }
@@ -308,7 +192,7 @@ AudioContext::Stream::isDecoded() const noexcept
 // Get the decoded data's size, will return 0 if the data is not decoded. Don't
 // rely on this behaviour to get the decoded state of the stream!
 size_t
-AudioContext::Stream::getDecodedDataSize() const noexcept
+AudioStream::getDecodedDataSize() const noexcept
 {
     return dataSize;
 }
@@ -316,28 +200,22 @@ AudioContext::Stream::getDecodedDataSize() const noexcept
 // Get the decoded data, safe to call even if the data is not decoded has the
 // pointer will be null. You must check it with an `if` statement.
 double *
-AudioContext::Stream::getDecodedData() const noexcept
+AudioStream::getDecodedData() const noexcept
 {
     return dataPtr;
 }
 
 // Get the chunk size of the decoded data
 size_t
-AudioContext::Stream::getDecodedChunkSize() const noexcept
+AudioStream::getDecodedChunkSize() const noexcept
 {
     return 512;
 }
 
 // Get the decalage of the decoded data
 size_t
-AudioContext::Stream::getDecodedDecalage() const noexcept
+AudioStream::getDecodedDecalage() const noexcept
 {
     constexpr size_t overlap = 128; // The overlap
     return getDecodedChunkSize() - overlap;
 }
-
-int
-AudioContext::Stream::getStreamIndex() const noexcept
-{
-    return streamIndexInAudioContext;
-}
diff --git a/src/Lib/Audio.hh b/src/Lib/Audio.hh
index 3259d1ffdec318d6bfeba89149de700d40dbea03..15d73c52a17324fa1eb26ff23fee737739b7f647 100644
--- a/src/Lib/Audio.hh
+++ b/src/Lib/Audio.hh
@@ -23,104 +23,66 @@ namespace Vivy
 {
 class AudioContext;
 
+// Hold all the data for a stream in an audio file. Should only be owned by
+// the AudioContext class.
+class AudioStream final : public AbstractMediaStream<AVMEDIA_TYPE_AUDIO> {
+    VIVY_UNMOVABLE_OBJECT(AudioStream)
+    using Super = AbstractMediaStream<AVMEDIA_TYPE_AUDIO>;
+
+public:
+    AudioStream(AVCodec *, AVFormatContext *, AVStream *, int index);
+    ~AudioStream() noexcept override;
+
+    // The non-owning view of the stream's data
+    using DataWeakPtr = std::weak_ptr<double[]>;
+
+    // Decode the stream
+    void decodeData();
+    void cleanUpData() noexcept;
+    bool isDecoded() const noexcept;
+    int getDataSampleRate() const noexcept;
+    size_t getDecodedDataSize() const noexcept;
+    size_t getDecodedChunkSize() const noexcept;
+    size_t getDecodedDecalage() const noexcept;
+    double *getDecodedData() const noexcept;
+
+    // Some getters
+    int getChannels() const noexcept;
+    int getSampleRate() const noexcept;
+    qint64 getBitRate() const noexcept;
+    QJsonObject getProperties() const noexcept override;
+
+private:
+    // Resampled frame data
+    SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter };
+    AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter };
+    double *dataPtr{ nullptr };
+    size_t dataSize{ 0 };
+
+    // Constants for the resampler
+    static constexpr uint resamplerChunkSize  = 512;
+    static constexpr uint resamplerOverlap    = 128;
+    static constexpr uint resamplerDecalage   = resamplerChunkSize - resamplerOverlap;
+    static constexpr uint resamplerSampleRate = 44100;
+};
+
 // The memory representation of an audio file. It is created inplace and should
 // not be moved around. It should be used to get the properties of an audio
 // file and its streams. The user can use a stream from the file and get data
 // from it.
-class AudioContext final {
+class AudioContext final : public AbstractMediaContext<AudioStream> {
     VIVY_UNMOVABLE_OBJECT(AudioContext)
+    using Super = AbstractMediaContext<AudioStream>;
 
 public:
-    // Hold all the data for a stream in an audio file. Should only be owned by
-    // the AudioContext class.
-    class Stream final {
-        VIVY_UNMOVABLE_OBJECT(Stream)
-
-        static inline Utils::DeleterFunctionType<double> dataDeleter =
-            std::bind_front(Utils::freePtrIfNotNull<double>, av_free);
-
-        static inline Utils::DeleterFunctionType<AVCodecContext> codecContexteleter =
-            std::bind_front(Utils::freePPtrIfNotNull<AVCodecContext>, avcodec_free_context);
-
-        static inline Utils::DeleterFunctionType<AVFrame> avFrameDeleter =
-            std::bind_front(Utils::freePPtrIfNotNull<AVFrame>, av_frame_free);
-
-        static inline Utils::DeleterFunctionType<SwrContext> swrContenxtDeleter =
-            std::bind_front(Utils::freePPtrIfNotNull<SwrContext>, swr_free);
-
-        // All the used types
-        using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>;
-        using DataPtr           = std::shared_ptr<double[]>;
-        using AVFramePtr        = std::unique_ptr<AVFrame, decltype(avFrameDeleter)>;
-        using SwrContextPtr     = std::unique_ptr<SwrContext, decltype(swrContenxtDeleter)>;
-
-    public:
-        Stream(AVCodec *, AVFormatContext *, AVStream *, int index);
-        ~Stream() noexcept;
-
-        // The non-owning view of the stream's data
-        using DataWeakPtr = std::weak_ptr<double[]>;
-
-        // Decode the stream
-        void decodeData();
-        void cleanUpData() noexcept;
-        bool isDecoded() const noexcept;
-        int getDataSampleRate() const noexcept;
-        size_t getDecodedDataSize() const noexcept;
-        size_t getDecodedChunkSize() const noexcept;
-        size_t getDecodedDecalage() const noexcept;
-        double *getDecodedData() const noexcept;
-
-        // Some getters
-        int getChannels() const noexcept;
-        int getSampleRate() const noexcept;
-        QString getName() const noexcept;
-        AVCodecID getCodecId() const noexcept;
-        qint64 getBitRate() const noexcept;
-
-        // Get the index from the audio context
-        int getStreamIndex() const noexcept;
-
-        QJsonObject getProperties() const noexcept;
-
-    private:
-        // Codec related informations
-        AVCodecID codecId{ AV_CODEC_ID_NONE };
-        AVCodec *codec{ nullptr };
-        AVCodecParameters *codecParams{ nullptr };
-        AVCodecContextPtr codecContext{ nullptr };
-
-        // Stream is held by AudioContext
-        AVStream *audioStream{ nullptr };
-
-        // Store the index of this stream
-        int streamIndexInAudioContext;
-
-        // Resampled frame data
-        AVFormatContext *dataFormat;
-        SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter };
-        AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter };
-        double *dataPtr{ nullptr };
-        size_t dataSize{ 0 };
-
-        // Constants for the resampler
-        static constexpr uint resamplerChunkSize  = 512;
-        static constexpr uint resamplerOverlap    = 128;
-        static constexpr uint resamplerDecalage   = resamplerChunkSize - resamplerOverlap;
-        static constexpr uint resamplerSampleRate = 44100;
-    };
-
-    using StreamPtr     = std::shared_ptr<Stream>;
-    using StreamWeakPtr = std::weak_ptr<Stream>;
+    using StreamPtr     = std::shared_ptr<AudioStream>;
+    using StreamWeakPtr = std::weak_ptr<AudioStream>;
 
 public:
     AudioContext(const QString &path);
 
-    StreamWeakPtr getStream(int) const noexcept;
-    StreamWeakPtr getDefaultStream() const noexcept;
-
-    QString getElementName() const noexcept;
-    QJsonDocument getProperties() const noexcept;
+    QString getElementName() const noexcept override;
+    QJsonDocument getProperties() const noexcept override;
 
 private:
     // Regarding the format