/*
 * Copyright 2024 Rémi Bernon for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "ntstatus.h"
#include "mfsrcsnk_private.h"

#include "wine/list.h"
#include "wine/debug.h"
#include "wine/winedmo.h"

WINE_DEFAULT_DEBUG_CHANNEL(mfplat);

#define DEFINE_MF_ASYNC_PARAMS(type)                                                               \
    static struct type *type ## _from_IUnknown(IUnknown *iface)                                    \
    {                                                                                              \
        return CONTAINING_RECORD(iface, struct type, IUnknown_iface);                              \
    }                                                                                              \
    static HRESULT WINAPI type ## _QueryInterface(IUnknown *iface, REFIID iid, void **out)         \
    {                                                                                              \
        if (IsEqualIID(iid, &IID_IUnknown))                                                        \
        {                                                                                          \
            IUnknown_AddRef(iface);                                                                \
            *out = iface;                                                                          \
            return S_OK;                                                                           \
        }                                                                                          \
        *out = NULL;                                                                               \
        return E_NOINTERFACE;                                                                      \
    }                                                                                              \
    static ULONG WINAPI type ## _AddRef(IUnknown *iface)                                           \
    {                                                                                              \
        struct type *object = type ## _from_IUnknown(iface);                                       \
        return InterlockedIncrement(&object->refcount);                                            \
    }                                                                                              \
    static ULONG WINAPI type ## _Release(IUnknown *iface)                                          \
    {                                                                                              \
        struct type *object = type ## _from_IUnknown(iface);                                       \
        ULONG ref = InterlockedDecrement(&object->refcount);                                       \
        if (!ref) type ## _ ## destroy(object);                                                    \
        return ref;                                                                                \
    }                                                                                              \
    static const IUnknownVtbl type ## _vtbl =                                                      \
    {                                                                                              \
        type ## _QueryInterface,                                                                   \
        type ## _AddRef,                                                                           \
        type ## _Release,                                                                          \
    };                                                                                             \
    static struct type *type ## _alloc(void)                                                       \
    {                                                                                              \
        struct type *object;                                                                       \
        if (!(object = calloc(1, sizeof(*object)))) return NULL;                                   \
        object->IUnknown_iface.lpVtbl = &type ## _vtbl;                                            \
        object->refcount = 1;                                                                      \
        return object;                                                                             \
    }

#define DEFINE_MF_ASYNC_CALLBACK_(type, name, impl_from, pfx, mem, expr)                           \
    static struct type *impl_from(IMFAsyncCallback *iface)                                         \
    {                                                                                              \
        return CONTAINING_RECORD(iface, struct type, mem);                                         \
    }                                                                                              \
    static HRESULT WINAPI pfx##_QueryInterface(IMFAsyncCallback *iface, REFIID iid, void **out)    \
    {                                                                                              \
        if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_IMFAsyncCallback))              \
        {                                                                                          \
            IMFAsyncCallback_AddRef((*out = iface));                                               \
            return S_OK;                                                                           \
        }                                                                                          \
        *out = NULL;                                                                               \
        return E_NOINTERFACE;                                                                      \
    }                                                                                              \
    static ULONG WINAPI pfx##_AddRef(IMFAsyncCallback *iface)                                      \
    {                                                                                              \
        struct type *object = impl_from(iface);                                                    \
        return IUnknown_AddRef((IUnknown *)(expr));                                                \
    }                                                                                              \
    static ULONG WINAPI pfx##_Release(IMFAsyncCallback *iface)                                     \
    {                                                                                              \
        struct type *object = impl_from(iface);                                                    \
        return IUnknown_Release((IUnknown *)(expr));                                               \
    }                                                                                              \
    static HRESULT WINAPI pfx##_GetParameters(IMFAsyncCallback *iface, DWORD *flags, DWORD *queue) \
    {                                                                                              \
        return E_NOTIMPL;                                                                          \
    }                                                                                              \
    static HRESULT WINAPI pfx##_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result)            \
    {                                                                                              \
        struct type *object = impl_from(iface);                                                    \
        return type##_##name(object, result);                                                      \
    }                                                                                              \
    static const IMFAsyncCallbackVtbl pfx##_vtbl =                                                 \
    {                                                                                              \
            pfx##_QueryInterface,                                                                  \
            pfx##_AddRef,                                                                          \
            pfx##_Release,                                                                         \
            pfx##_GetParameters,                                                                   \
            pfx##_Invoke,                                                                          \
    };

#define DEFINE_MF_ASYNC_CALLBACK(type, name, base_iface)                                           \
    DEFINE_MF_ASYNC_CALLBACK_(type, name, type##_from_##name, type##_##name, name##_iface, &object->base_iface)

struct object_entry
{
    struct list entry;
    IUnknown *object;
};

static void object_entry_destroy(struct object_entry *entry)
{
    if (entry->object) IUnknown_Release( entry->object );
    free(entry);
}

static HRESULT object_entry_create(IUnknown *object, struct object_entry **out)
{
    struct object_entry *entry;

    if (!(entry = calloc(1, sizeof(*entry)))) return E_OUTOFMEMORY;
    if ((entry->object = object)) IUnknown_AddRef( entry->object );

    *out = entry;
    return S_OK;
}

static HRESULT object_queue_push(struct list *queue, IUnknown *object)
{
    struct object_entry *entry;
    HRESULT hr;

    if (SUCCEEDED(hr = object_entry_create(object, &entry)))
        list_add_tail(queue, &entry->entry);

    return hr;
}

static HRESULT object_queue_pop(struct list *queue, IUnknown **object)
{
    struct object_entry *entry;
    struct list *ptr;

    if (!(ptr = list_head(queue))) return E_PENDING;
    entry = LIST_ENTRY(ptr, struct object_entry, entry);

    if ((*object = entry->object)) IUnknown_AddRef(*object);
    list_remove(&entry->entry);
    object_entry_destroy(entry);

    return S_OK;
}

static void object_queue_clear(struct list *queue)
{
    IUnknown *object;
    while (object_queue_pop(queue, &object) != E_PENDING)
        if (object) IUnknown_Release(object);
}

struct async_start_params
{
    IUnknown IUnknown_iface;
    LONG refcount;
    IMFPresentationDescriptor *descriptor;
    GUID format;
    PROPVARIANT position;
};

static void async_start_params_destroy(struct async_start_params *params)
{
    IMFPresentationDescriptor_Release(params->descriptor);
    PropVariantClear(&params->position);
    free(params);
}

DEFINE_MF_ASYNC_PARAMS(async_start_params);

static HRESULT async_start_params_create(IMFPresentationDescriptor *descriptor, const GUID *time_format,
        const PROPVARIANT *position, IUnknown **out)
{
    struct async_start_params *params;

    if (!(params = async_start_params_alloc())) return E_OUTOFMEMORY;
    params->descriptor = descriptor;
    IMFPresentationDescriptor_AddRef(descriptor);
    params->format = *time_format;
    PropVariantCopy(&params->position, position);

    *out = &params->IUnknown_iface;
    return S_OK;
}

struct async_set_rate_params
{
    IUnknown IUnknown_iface;
    LONG refcount;
    float rate;
    BOOL thin;
};

static void async_set_rate_params_destroy(struct async_set_rate_params *params)
{
    free(params);
}

DEFINE_MF_ASYNC_PARAMS(async_set_rate_params);

static HRESULT async_set_rate_params_create(float rate, BOOL thin, IUnknown **out)
{
    struct async_set_rate_params *params;

    if (!(params = async_set_rate_params_alloc())) return E_OUTOFMEMORY;
    params->rate = rate;
    params->thin = thin;

    *out = &params->IUnknown_iface;
    return S_OK;
}

struct media_stream
{
    IMFMediaStream IMFMediaStream_iface;
    IMFAsyncCallback async_request_iface;
    IMFAsyncCallback async_request_thin_iface;
    LONG refcount;

    IMFMediaSource *source;
    IMFMediaEventQueue *queue;
    IMFStreamDescriptor *descriptor;
    struct list samples;
    struct list tokens;

    BOOL active;
    BOOL eos;
    BOOL thin;
    BOOL pending_thin;
};

struct media_source
{
    IMFMediaSource IMFMediaSource_iface;
    IMFGetService IMFGetService_iface;
    IMFRateSupport IMFRateSupport_iface;
    IMFRateControl IMFRateControl_iface;
    IMFAsyncCallback async_create_iface;
    IMFAsyncCallback async_start_iface;
    IMFAsyncCallback async_stop_iface;
    IMFAsyncCallback async_pause_iface;
    IMFAsyncCallback async_read_iface;
    IMFAsyncCallback async_set_rate_iface;
    LONG refcount;

    CRITICAL_SECTION cs;
    IMFMediaEventQueue *queue;
    IMFByteStream *stream;
    WCHAR *url;
    float rate;
    BOOL thin;

    struct winedmo_demuxer winedmo_demuxer;
    struct winedmo_stream winedmo_stream;
    UINT64 file_size;
    INT64 duration;
    UINT stream_count;
    WCHAR mime_type[256];

    UINT *stream_map;
    struct media_stream **streams;

    enum
    {
        SOURCE_STOPPED,
        SOURCE_PAUSED,
        SOURCE_RUNNING,
        SOURCE_SHUTDOWN,
    } state;
    UINT pending_reads;
};

static struct media_source *media_source_from_IMFMediaSource(IMFMediaSource *iface)
{
    return CONTAINING_RECORD(iface, struct media_source, IMFMediaSource_iface);
}

static void queue_media_event_value(IMFMediaEventQueue *queue, MediaEventType type, const PROPVARIANT *value)
{
    HRESULT hr;
    if (FAILED(hr = IMFMediaEventQueue_QueueEventParamVar(queue, type, &GUID_NULL, S_OK, value)))
        ERR("Failed to queue event of type %#lx to queue %p, hr %#lx\n", type, queue, hr);
}

static void media_stream_set_thin(struct media_stream *stream, BOOL thin)
{
    if (stream->thin != thin)
    {
        PROPVARIANT param = {.vt = VT_INT, .iVal = thin};
        stream->thin = thin;
        queue_media_event_value(stream->queue, MEStreamThinMode, &param);
    }
}

static void media_stream_send_sample(struct media_stream *stream, IMFSample *sample, IUnknown *token)
{
    HRESULT hr = S_OK;

    if (!token || SUCCEEDED(hr = IMFSample_SetUnknown(sample, &MFSampleExtension_Token, token)))
    {
        /* thinning stays enabled until a sample is sent, always a keyframe as it was enabled */
        if (!stream->pending_thin) media_stream_set_thin(stream, FALSE);
        hr = IMFMediaEventQueue_QueueEventParamUnk(stream->queue, MEMediaSample,
                &GUID_NULL, S_OK, (IUnknown *)sample);
    }

    if (FAILED(hr)) ERR("Failed to send stream %p sample, hr %#lx\n", stream, hr);
}

static struct media_stream *media_stream_from_index(struct media_source *source, UINT index)
{
    UINT i;

    for (i = 0; i < source->stream_count; i++)
        if (source->stream_map[i] == index)
            return source->streams[i];

    WARN("Failed to find stream with index %u\n", index);
    return NULL;
}

static BOOL media_stream_accepts_sample(struct media_stream *stream, IMFSample *sample, BOOL thin)
{
    BOOL thinning = thin || stream->thin;
    UINT32 keyframe;

    if (thinning && SUCCEEDED(IMFSample_GetUINT32(sample, &MFSampleExtension_CleanPoint, &keyframe)))
        return keyframe;

    return !thinning;
}

static HRESULT media_source_send_sample(struct media_source *source, UINT index, IMFSample *sample)
{
    struct media_stream *stream;
    IUnknown *token;
    HRESULT hr;

    if (!(stream = media_stream_from_index(source, index)) || !stream->active)
        return S_FALSE;

    if (list_empty(&stream->tokens))
    {
        if (FAILED(hr = object_queue_push(&stream->samples, (IUnknown *)sample)))
            return hr;
        return S_FALSE;
    }

    if (!media_stream_accepts_sample(stream, sample, stream->pending_thin))
    {
        /* thinning stays disabled until a sample is skipped */
        if (stream->pending_thin) media_stream_set_thin(stream, TRUE);
        return S_FALSE;
    }

    object_queue_pop(&stream->tokens, &token);
    media_stream_send_sample(stream, sample, token);
    if (token) IUnknown_Release(token);
    return S_OK;
}

static void queue_media_event_object(IMFMediaEventQueue *queue, MediaEventType type, IUnknown *object)
{
    HRESULT hr;
    if (FAILED(hr = IMFMediaEventQueue_QueueEventParamUnk(queue, type, &GUID_NULL, S_OK, object)))
        ERR("Failed to queue event of type %#lx to queue %p, hr %#lx\n", type, queue, hr);
}

static void queue_media_source_read(struct media_source *source)
{
    HRESULT hr;

    if (FAILED(hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, &source->async_read_iface, NULL)))
        ERR("Failed to queue async read for source %p, hr %#lx\n", source, hr);
    source->pending_reads++;
}

static void media_stream_start(struct media_stream *stream, UINT index, const PROPVARIANT *position)
{
    struct media_source *source = media_source_from_IMFMediaSource(stream->source);
    BOOL starting = source->state == SOURCE_STOPPED, seeking = !starting && position->vt != VT_EMPTY;
    BOOL was_active = !starting && stream->active;

    TRACE("stream %p, index %u, position %s\n", stream, index, debugstr_propvar(position));

    if (position->vt != VT_EMPTY)
    {
        object_queue_clear(&stream->tokens);
        object_queue_clear(&stream->samples);
    }

    if (stream->active)
    {
        queue_media_event_object(source->queue, was_active ? MEUpdatedStream : MENewStream,
                (IUnknown *)&stream->IMFMediaStream_iface);
        queue_media_event_value(stream->queue, seeking ? MEStreamSeeked : MEStreamStarted, position);
    }

    if (position->vt == VT_EMPTY && stream->active)
    {
        struct list samples = LIST_INIT(samples);
        IMFSample *sample;
        UINT token_count;

        list_move_head(&samples, &stream->samples);
        while (object_queue_pop(&samples, (IUnknown **)&sample) != E_PENDING)
        {
            media_source_send_sample(source, index, sample);
            IMFSample_Release(sample);
        }

        token_count = list_count(&stream->tokens);
        while (source->pending_reads < token_count)
            queue_media_source_read(source);
    }
}

static HRESULT media_source_start(struct media_source *source, IMFPresentationDescriptor *descriptor,
        GUID *format, PROPVARIANT *position)
{
    BOOL starting = source->state == SOURCE_STOPPED, seeking = !starting && position->vt != VT_EMPTY;
    NTSTATUS status;
    DWORD i, count;
    HRESULT hr;

    TRACE("source %p, descriptor %p, format %s, position %s\n", source, descriptor,
            debugstr_guid(format), debugstr_propvar(position));

    if (source->state == SOURCE_SHUTDOWN)
        return MF_E_SHUTDOWN;

    if (source->state == SOURCE_STOPPED && position->vt == VT_EMPTY)
    {
        position->vt = VT_I8;
        position->hVal.QuadPart = 0;
    }

    for (i = 0; i < source->stream_count; i++)
    {
        struct media_stream *stream = source->streams[i];
        if (position->vt != VT_EMPTY)
            stream->eos = FALSE;
        stream->active = FALSE;
    }

    if (FAILED(hr = IMFPresentationDescriptor_GetStreamDescriptorCount(descriptor, &count)))
        WARN("Failed to get presentation descriptor stream count, hr %#lx\n", hr);

    for (i = 0; i < count; i++)
    {
        IMFStreamDescriptor *stream_descriptor;
        BOOL selected;
        DWORD id;

        if (FAILED(hr = IMFPresentationDescriptor_GetStreamDescriptorByIndex(descriptor, i,
                &selected, &stream_descriptor)))
            break;

        if (FAILED(hr = IMFStreamDescriptor_GetStreamIdentifier(stream_descriptor, &id)))
            WARN("Failed to get stream descriptor id, hr %#lx\n", hr);
        else if (id > source->stream_count)
            WARN("Invalid stream descriptor id %lu, hr %#lx\n", id, hr);
        else
        {
            struct media_stream *stream = source->streams[id - 1];
            stream->active = selected;
        }

        IMFStreamDescriptor_Release(stream_descriptor);
    }

    for (i = 0; i < source->stream_count; i++)
    {
        struct media_stream *stream = source->streams[i];
        media_stream_start(stream, source->stream_map[i], position);
    }

    if (position->vt == VT_I8 && (status = winedmo_demuxer_seek(source->winedmo_demuxer, position->hVal.QuadPart)))
        WARN("Failed to seek to %I64d, status %#lx\n", position->hVal.QuadPart, status);
    source->state = SOURCE_RUNNING;

    queue_media_event_value(source->queue, seeking ? MESourceSeeked : MESourceStarted, position);
    return hr;
}

static HRESULT media_source_async_start(struct media_source *source, IMFAsyncResult *result)
{
    struct async_start_params *params;
    IUnknown *state;
    HRESULT hr;

    if (!(state = IMFAsyncResult_GetStateNoAddRef(result))) return E_INVALIDARG;
    params = async_start_params_from_IUnknown(state);

    EnterCriticalSection(&source->cs);

    if (FAILED(hr = media_source_start(source, params->descriptor, &params->format, &params->position)))
        WARN("Failed to start source %p, hr %#lx\n", source, hr);

    LeaveCriticalSection(&source->cs);

    return hr;
}

DEFINE_MF_ASYNC_CALLBACK(media_source, async_start, IMFMediaSource_iface)

static void media_stream_stop(struct media_stream *stream)
{
    TRACE("stream %p\n", stream);

    object_queue_clear(&stream->tokens);
    object_queue_clear(&stream->samples);

    if (stream->active)
        queue_media_event_value(stream->queue, MEStreamStopped, NULL);
}

static HRESULT media_source_stop(struct media_source *source)
{
    unsigned int i;

    TRACE("source %p\n", source);

    if (source->state == SOURCE_SHUTDOWN)
        return MF_E_SHUTDOWN;

    for (i = 0; i < source->stream_count; i++)
    {
        struct media_stream *stream = source->streams[i];
        media_stream_stop(stream);
    }

    source->state = SOURCE_STOPPED;
    queue_media_event_value(source->queue, MESourceStopped, NULL);
    return S_OK;
}

static HRESULT media_source_async_stop(struct media_source *source, IMFAsyncResult *result)
{
    HRESULT hr;

    EnterCriticalSection(&source->cs);

    if (FAILED(hr = media_source_stop(source)))
        WARN("Failed to stop source %p, hr %#lx\n", source, hr);

    LeaveCriticalSection(&source->cs);

    return hr;
}

DEFINE_MF_ASYNC_CALLBACK(media_source, async_stop, IMFMediaSource_iface)

static void media_stream_pause(struct media_stream *stream)
{
    TRACE("stream %p\n", stream);

    if (stream->active)
        queue_media_event_value(stream->queue, MEStreamPaused, NULL);
}

static HRESULT media_source_pause(struct media_source *source)
{
    unsigned int i;

    TRACE("source %p\n", source);

    if (source->state == SOURCE_SHUTDOWN)
        return MF_E_SHUTDOWN;

    for (i = 0; i < source->stream_count; i++)
    {
        struct media_stream *stream = source->streams[i];
        media_stream_pause(stream);
    }

    source->state = SOURCE_PAUSED;
    queue_media_event_value(source->queue, MESourcePaused, NULL);
    return S_OK;
}

static HRESULT media_source_async_pause(struct media_source *source, IMFAsyncResult *result)
{
    HRESULT hr;

    EnterCriticalSection(&source->cs);

    if (FAILED(hr = media_source_pause(source)))
        WARN("Failed to pause source %p, hr %#lx\n", source, hr);

    LeaveCriticalSection(&source->cs);

    return hr;
}

DEFINE_MF_ASYNC_CALLBACK(media_source, async_pause, IMFMediaSource_iface)

static HRESULT create_media_buffer_sample(UINT buffer_size, IMFSample **sample, IMediaBuffer **media_buffer)
{
    IMFMediaBuffer *buffer;
    HRESULT hr;

    if (FAILED(hr = MFCreateSample(sample)))
        return hr;
    if (SUCCEEDED(hr = MFCreateMemoryBuffer( buffer_size, &buffer )))
    {
        if (SUCCEEDED(hr = IMFSample_AddBuffer(*sample, buffer)))
            hr = MFCreateLegacyMediaBufferOnMFMediaBuffer(*sample, buffer, 0, media_buffer);
        IMFMediaBuffer_Release(buffer);
    }
    if (FAILED(hr))
    {
        IMFSample_Release(*sample);
        *sample = NULL;
    }
    return hr;
}

static HRESULT demuxer_read_sample(struct winedmo_demuxer demuxer, UINT *index, IMFSample **out)
{
    UINT buffer_size = 0x1000;
    IMFSample *sample;
    HRESULT hr;

    do
    {
        DMO_OUTPUT_DATA_BUFFER output = {0};
        NTSTATUS status;

        if (FAILED(hr = create_media_buffer_sample(buffer_size, &sample, &output.pBuffer)))
            return hr;
        if ((status = winedmo_demuxer_read(demuxer, index, &output, &buffer_size)))
        {
            if (status == STATUS_BUFFER_TOO_SMALL) hr = E_PENDING;
            else if (status == STATUS_END_OF_FILE) hr = MF_E_END_OF_STREAM;
            else hr = HRESULT_FROM_NT(status);
            IMFSample_Release(sample);
            sample = NULL;
        }
        IMediaBuffer_Release(output.pBuffer);
    }
    while (hr == E_PENDING);

    *out = sample;
    return hr;
}

static HRESULT media_source_send_eos(struct media_source *source, struct media_stream *stream)
{
    PROPVARIANT empty = {.vt = VT_EMPTY};
    UINT i;

    if (stream->active && !stream->eos && list_empty(&stream->samples))
    {
        queue_media_event_value(stream->queue, MEEndOfStream, &empty);
        stream->eos = TRUE;
    }

    for (i = 0; i < source->stream_count; i++)
    {
        struct media_stream *other = source->streams[i];
        if (other->active && !other->eos) return S_OK;
    }

    queue_media_event_value(source->queue, MEEndOfPresentation, &empty);
    return S_OK;
}

static HRESULT media_source_read(struct media_source *source)
{
    IMFSample *sample;
    UINT i, index;
    HRESULT hr;

    if (source->state != SOURCE_RUNNING)
        return S_OK;

    if (FAILED(hr = demuxer_read_sample(source->winedmo_demuxer, &index, &sample)) && hr != MF_E_END_OF_STREAM)
    {
        WARN("Failed to read stream %u data, hr %#lx\n", index, hr);
        return hr;
    }

    if (hr == MF_E_END_OF_STREAM)
    {
        for (i = 0; i < source->stream_count; i++)
            media_source_send_eos(source, source->streams[i]);
        return S_OK;
    }

    if ((hr = media_source_send_sample(source, index, sample)) == S_FALSE)
        queue_media_source_read(source);
    IMFSample_Release(sample);

    return hr;
}

static HRESULT media_source_async_read(struct media_source *source, IMFAsyncResult *result)
{
    HRESULT hr;

    EnterCriticalSection(&source->cs);
    source->pending_reads--;

    if (FAILED(hr = media_source_read(source)))
        WARN("Failed to request sample, hr %#lx\n", hr);

    LeaveCriticalSection(&source->cs);

    return S_OK;
}

DEFINE_MF_ASYNC_CALLBACK(media_source, async_read, IMFMediaSource_iface)

static struct media_stream *media_stream_from_IMFMediaStream(IMFMediaStream *iface)
{
    return CONTAINING_RECORD(iface, struct media_stream, IMFMediaStream_iface);
}

static HRESULT WINAPI media_stream_QueryInterface(IMFMediaStream *iface, REFIID riid, void **out)
{
    struct media_stream *stream = media_stream_from_IMFMediaStream(iface);

    TRACE("stream %p, riid %s, out %p\n", stream, debugstr_guid(riid), out);

    if (IsEqualIID(riid, &IID_IUnknown)
            || IsEqualIID(riid, &IID_IMFMediaEventGenerator)
            || IsEqualIID(riid, &IID_IMFMediaStream))
    {
        IMFMediaStream_AddRef(&stream->IMFMediaStream_iface);
        *out = &stream->IMFMediaStream_iface;
        return S_OK;
    }

    FIXME("Unsupported interface %s\n", debugstr_guid(riid));
    *out = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI media_stream_AddRef(IMFMediaStream *iface)
{
    struct media_stream *stream = media_stream_from_IMFMediaStream(iface);
    ULONG refcount = InterlockedIncrement(&stream->refcount);
    TRACE("stream %p, refcount %ld\n", stream, refcount);
    return refcount;
}

static ULONG WINAPI media_stream_Release(IMFMediaStream *iface)
{
    struct media_stream *stream = media_stream_from_IMFMediaStream(iface);
    ULONG refcount = InterlockedDecrement(&stream->refcount);

    TRACE("stream %p, refcount %ld\n", stream, refcount);

    if (!refcount)
    {
        stream->active = FALSE;
        media_stream_stop(stream);
        IMFMediaSource_Release(stream->source);
        IMFStreamDescriptor_Release(stream->descriptor);
        IMFMediaEventQueue_Release(stream->queue);
        free(stream);
    }

    return refcount;
}

static HRESULT WINAPI media_stream_GetEvent(IMFMediaStream *iface, DWORD flags, IMFMediaEvent **event)
{
    struct media_stream *stream = media_stream_from_IMFMediaStream(iface);
    TRACE("stream %p, flags %#lx, event %p\n", stream, flags, event);
    return IMFMediaEventQueue_GetEvent(stream->queue, flags, event);
}

static HRESULT WINAPI media_stream_BeginGetEvent(IMFMediaStream *iface, IMFAsyncCallback *callback, IUnknown *state)
{
    struct media_stream *stream = media_stream_from_IMFMediaStream(iface);
    TRACE("stream %p, callback %p, state %p\n", stream, callback, state);
    return IMFMediaEventQueue_BeginGetEvent(stream->queue, callback, state);
}

static HRESULT WINAPI media_stream_EndGetEvent(IMFMediaStream *iface, IMFAsyncResult *result, IMFMediaEvent **event)
{
    struct media_stream *stream = media_stream_from_IMFMediaStream(iface);
    TRACE("stream %p, result %p, event %p\n", stream, result, event);
    return IMFMediaEventQueue_EndGetEvent(stream->queue, result, event);
}

static HRESULT WINAPI media_stream_QueueEvent(IMFMediaStream *iface, MediaEventType event_type, REFGUID ext_type,
        HRESULT hr, const PROPVARIANT *value)
{
    struct media_stream *stream = media_stream_from_IMFMediaStream(iface);
    TRACE("stream %p, event_type %#lx, ext_type %s, hr %#lx, value %p\n", stream, event_type, debugstr_guid(ext_type), hr, value);
    return IMFMediaEventQueue_QueueEventParamVar(stream->queue, event_type, ext_type, hr, value);
}

static HRESULT WINAPI media_stream_GetMediaSource(IMFMediaStream *iface, IMFMediaSource **out)
{
    struct media_stream *stream = media_stream_from_IMFMediaStream(iface);
    struct media_source *source = media_source_from_IMFMediaSource(stream->source);
    HRESULT hr = S_OK;

    TRACE("stream %p, out %p\n", stream, out);

    EnterCriticalSection(&source->cs);

    if (source->state == SOURCE_SHUTDOWN)
        hr = MF_E_SHUTDOWN;
    else
    {
        IMFMediaSource_AddRef(&source->IMFMediaSource_iface);
        *out = &source->IMFMediaSource_iface;
    }

    LeaveCriticalSection(&source->cs);

    return hr;
}

static HRESULT WINAPI media_stream_GetStreamDescriptor(IMFMediaStream* iface, IMFStreamDescriptor **descriptor)
{
    struct media_stream *stream = media_stream_from_IMFMediaStream(iface);
    struct media_source *source = media_source_from_IMFMediaSource(stream->source);
    HRESULT hr = S_OK;

    TRACE("stream %p, descriptor %p\n", stream, descriptor);

    EnterCriticalSection(&source->cs);

    if (source->state == SOURCE_SHUTDOWN)
        hr = MF_E_SHUTDOWN;
    else
    {
        IMFStreamDescriptor_AddRef(stream->descriptor);
        *descriptor = stream->descriptor;
    }

    LeaveCriticalSection(&source->cs);

    return hr;
}

static HRESULT media_stream_pop_sample(struct media_stream *stream, IMFSample **pSample)
{
    HRESULT hr;

    while (SUCCEEDED(hr = object_queue_pop(&stream->samples, (IUnknown **)pSample))
            && !media_stream_accepts_sample(stream, *pSample, stream->pending_thin))
    {
        /* thinning stays disabled until a sample is skipped */
        if (stream->pending_thin) media_stream_set_thin(stream, TRUE);
        IMFSample_Release(*pSample);
    }

    return hr;
}

static HRESULT media_source_request_stream_sample(struct media_source *source, struct media_stream *stream, IUnknown *token)
{
    IMFSample *sample;
    HRESULT hr = S_OK;

    if (source->state == SOURCE_SHUTDOWN)
        return MF_E_SHUTDOWN;

    if (source->state == SOURCE_RUNNING && SUCCEEDED(hr = media_stream_pop_sample(stream, &sample)))
    {
        media_stream_send_sample(stream, sample, token);
        IMFSample_Release(sample);
        return S_OK;
    }

    if (SUCCEEDED(hr = object_queue_push(&stream->tokens, token)) && source->state == SOURCE_RUNNING)
        queue_media_source_read(source);
    return hr;
}

static HRESULT media_stream_async_request(struct media_stream *stream, IMFAsyncResult *result)
{
    struct media_source *source = media_source_from_IMFMediaSource(stream->source);
    IUnknown *token = IMFAsyncResult_GetStateNoAddRef(result);
    HRESULT hr = S_OK;

    EnterCriticalSection(&source->cs);
    stream->pending_thin = FALSE;
    hr = media_source_request_stream_sample(source, stream, token);
    LeaveCriticalSection(&source->cs);

    return hr;
}

DEFINE_MF_ASYNC_CALLBACK(media_stream, async_request, IMFMediaStream_iface)

static HRESULT media_stream_async_request_thin(struct media_stream *stream, IMFAsyncResult *result)
{
    struct media_source *source = media_source_from_IMFMediaSource(stream->source);
    IUnknown *token = IMFAsyncResult_GetStateNoAddRef(result);
    HRESULT hr = S_OK;

    EnterCriticalSection(&source->cs);
    stream->pending_thin = TRUE;
    hr = media_source_request_stream_sample(source, stream, token);
    LeaveCriticalSection(&source->cs);

    return hr;
}

DEFINE_MF_ASYNC_CALLBACK(media_stream, async_request_thin, IMFMediaStream_iface)

static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown *token)
{
    struct media_stream *stream = media_stream_from_IMFMediaStream(iface);
    struct media_source *source = media_source_from_IMFMediaSource(stream->source);
    HRESULT hr;

    TRACE("stream %p, token %p\n", stream, token);

    EnterCriticalSection(&source->cs);

    if (source->state == SOURCE_SHUTDOWN)
        hr = MF_E_SHUTDOWN;
    else if (!stream->active)
        hr = MF_E_MEDIA_SOURCE_WRONGSTATE;
    else if (stream->eos)
        hr = MF_E_END_OF_STREAM;
    else if (source->thin)
        hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, &stream->async_request_thin_iface, token);
    else
        hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, &stream->async_request_iface, token);

    LeaveCriticalSection(&source->cs);

    return hr;
}

static const IMFMediaStreamVtbl media_stream_vtbl =
{
    media_stream_QueryInterface,
    media_stream_AddRef,
    media_stream_Release,
    media_stream_GetEvent,
    media_stream_BeginGetEvent,
    media_stream_EndGetEvent,
    media_stream_QueueEvent,
    media_stream_GetMediaSource,
    media_stream_GetStreamDescriptor,
    media_stream_RequestSample,
};

static HRESULT media_stream_create(IMFMediaSource *source, IMFStreamDescriptor *descriptor, struct media_stream **out)
{
    struct media_stream *object;
    HRESULT hr;

    TRACE("source %p, descriptor %p, out %p\n", source, descriptor, out);

    if (!(object = calloc(1, sizeof(*object))))
        return E_OUTOFMEMORY;

    object->IMFMediaStream_iface.lpVtbl = &media_stream_vtbl;
    object->async_request_iface.lpVtbl = &media_stream_async_request_vtbl;
    object->async_request_thin_iface.lpVtbl = &media_stream_async_request_thin_vtbl;
    object->refcount = 1;

    if (FAILED(hr = MFCreateEventQueue(&object->queue)))
    {
        free(object);
        return hr;
    }

    IMFMediaSource_AddRef((object->source = source));
    IMFStreamDescriptor_AddRef((object->descriptor = descriptor));

    list_init(&object->tokens);
    list_init(&object->samples);

    TRACE("Created stream object %p\n", object);

    *out = object;
    return S_OK;
}

static struct media_source *media_source_from_IMFGetService(IMFGetService *iface)
{
    return CONTAINING_RECORD(iface, struct media_source, IMFGetService_iface);
}

static HRESULT WINAPI media_source_IMFGetService_QueryInterface(IMFGetService *iface, REFIID riid, void **obj)
{
    struct media_source *source = media_source_from_IMFGetService(iface);
    return IMFMediaSource_QueryInterface(&source->IMFMediaSource_iface, riid, obj);
}

static ULONG WINAPI media_source_IMFGetService_AddRef(IMFGetService *iface)
{
    struct media_source *source = media_source_from_IMFGetService(iface);
    return IMFMediaSource_AddRef(&source->IMFMediaSource_iface);
}

static ULONG WINAPI media_source_IMFGetService_Release(IMFGetService *iface)
{
    struct media_source *source = media_source_from_IMFGetService(iface);
    return IMFMediaSource_Release(&source->IMFMediaSource_iface);
}

static HRESULT WINAPI media_source_IMFGetService_GetService(IMFGetService *iface, REFGUID service,
        REFIID riid, void **obj)
{
    struct media_source *source = media_source_from_IMFGetService(iface);

    TRACE("source %p, service %s, riid %s, obj %p\n", source, debugstr_guid(service), debugstr_guid(riid), obj);

    if (IsEqualGUID(service, &MF_RATE_CONTROL_SERVICE))
    {
        if (IsEqualIID(riid, &IID_IMFRateSupport))
        {
            IMFRateSupport_AddRef(&source->IMFRateSupport_iface);
            *obj = &source->IMFRateSupport_iface;
            return S_OK;
        }
        if (IsEqualIID(riid, &IID_IMFRateControl))
        {
            IMFRateControl_AddRef(&source->IMFRateControl_iface);
            *obj = &source->IMFRateControl_iface;
            return S_OK;
        }
    }

    FIXME("Unsupported service %s / riid %s\n", debugstr_guid(service), debugstr_guid(riid));
    *obj = NULL;
    return E_NOINTERFACE;
}

static const IMFGetServiceVtbl media_source_IMFGetService_vtbl =
{
    media_source_IMFGetService_QueryInterface,
    media_source_IMFGetService_AddRef,
    media_source_IMFGetService_Release,
    media_source_IMFGetService_GetService,
};

static struct media_source *media_source_from_IMFRateSupport(IMFRateSupport *iface)
{
    return CONTAINING_RECORD(iface, struct media_source, IMFRateSupport_iface);
}

static HRESULT WINAPI media_source_IMFRateSupport_QueryInterface(IMFRateSupport *iface, REFIID riid, void **obj)
{
    struct media_source *source = media_source_from_IMFRateSupport(iface);
    return IMFMediaSource_QueryInterface(&source->IMFMediaSource_iface, riid, obj);
}

static ULONG WINAPI media_source_IMFRateSupport_AddRef(IMFRateSupport *iface)
{
    struct media_source *source = media_source_from_IMFRateSupport(iface);
    return IMFMediaSource_AddRef(&source->IMFMediaSource_iface);
}

static ULONG WINAPI media_source_IMFRateSupport_Release(IMFRateSupport *iface)
{
    struct media_source *source = media_source_from_IMFRateSupport(iface);
    return IMFMediaSource_Release(&source->IMFMediaSource_iface);
}

static HRESULT WINAPI media_source_IMFRateSupport_GetSlowestRate(IMFRateSupport *iface,
        MFRATE_DIRECTION direction, BOOL thin, float *rate)
{
    struct media_source *source = media_source_from_IMFRateSupport(iface);
    TRACE("source %p, direction %d, thin %d, rate %p\n", source, direction, thin, rate);
    *rate = 0.0f;
    return S_OK;
}

static HRESULT WINAPI media_source_IMFRateSupport_GetFastestRate(IMFRateSupport *iface,
        MFRATE_DIRECTION direction, BOOL thin, float *rate)
{
    struct media_source *source = media_source_from_IMFRateSupport(iface);
    TRACE("source %p, direction %d, thin %d, rate %p\n", source, direction, thin, rate);
    *rate = direction == MFRATE_FORWARD ? 1e6f : -1e6f;
    return S_OK;
}

static HRESULT WINAPI media_source_IMFRateSupport_IsRateSupported(IMFRateSupport *iface, BOOL thin,
        float rate, float *nearest_rate)
{
    struct media_source *source = media_source_from_IMFRateSupport(iface);
    TRACE("source %p, thin %d, rate %f, nearest_rate %p\n", source, thin, rate, nearest_rate);
    if (nearest_rate) *nearest_rate = rate;
    return rate >= -1e6f && rate <= 1e6f ? S_OK : MF_E_UNSUPPORTED_RATE;
}

static const IMFRateSupportVtbl media_source_IMFRateSupport_vtbl =
{
    media_source_IMFRateSupport_QueryInterface,
    media_source_IMFRateSupport_AddRef,
    media_source_IMFRateSupport_Release,
    media_source_IMFRateSupport_GetSlowestRate,
    media_source_IMFRateSupport_GetFastestRate,
    media_source_IMFRateSupport_IsRateSupported,
};

static struct media_source *media_source_from_IMFRateControl(IMFRateControl *iface)
{
    return CONTAINING_RECORD(iface, struct media_source, IMFRateControl_iface);
}

static HRESULT WINAPI media_source_IMFRateControl_QueryInterface(IMFRateControl *iface, REFIID riid, void **obj)
{
    struct media_source *source = media_source_from_IMFRateControl(iface);
    return IMFMediaSource_QueryInterface(&source->IMFMediaSource_iface, riid, obj);
}

static ULONG WINAPI media_source_IMFRateControl_AddRef(IMFRateControl *iface)
{
    struct media_source *source = media_source_from_IMFRateControl(iface);
    return IMFMediaSource_AddRef(&source->IMFMediaSource_iface);
}

static ULONG WINAPI media_source_IMFRateControl_Release(IMFRateControl *iface)
{
    struct media_source *source = media_source_from_IMFRateControl(iface);
    return IMFMediaSource_Release(&source->IMFMediaSource_iface);
}

static HRESULT media_source_async_set_rate(struct media_source *source, IMFAsyncResult *result)
{
    struct async_set_rate_params *params;
    IUnknown *state;

    if (!(state = IMFAsyncResult_GetStateNoAddRef(result))) return E_INVALIDARG;
    params = async_set_rate_params_from_IUnknown(state);

    EnterCriticalSection(&source->cs);
    source->rate = params->rate;
    source->thin = params->thin;
    LeaveCriticalSection(&source->cs);

    queue_media_event_value(source->queue, MESourceRateChanged, NULL);
    return S_OK;
}

DEFINE_MF_ASYNC_CALLBACK(media_source, async_set_rate, IMFMediaSource_iface)

static HRESULT WINAPI media_source_IMFRateControl_SetRate(IMFRateControl *iface, BOOL thin, float rate)
{
    struct media_source *source = media_source_from_IMFRateControl(iface);
    IUnknown *params;
    HRESULT hr;

    FIXME("source %p, thin %d, rate %f, stub!\n", source, thin, rate);

    if (rate < 0.0f)
        return MF_E_REVERSE_UNSUPPORTED;

    if (FAILED(hr = IMFRateSupport_IsRateSupported(&source->IMFRateSupport_iface, thin, rate, NULL)))
        return hr;

    if (FAILED(hr = async_set_rate_params_create(rate, thin, &params)))
        return hr;

    hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, &source->async_set_rate_iface, params);
    IUnknown_Release(params);

    return hr;
}

static HRESULT WINAPI media_source_IMFRateControl_GetRate(IMFRateControl *iface, BOOL *thin, float *rate)
{
    struct media_source *source = media_source_from_IMFRateControl(iface);

    TRACE("source %p, thin %p, rate %p\n", source, thin, rate);

    EnterCriticalSection(&source->cs);
    *rate = source->rate;
    if (thin)
        *thin = source->thin;
    LeaveCriticalSection(&source->cs);

    return S_OK;
}

static const IMFRateControlVtbl media_source_IMFRateControl_vtbl =
{
    media_source_IMFRateControl_QueryInterface,
    media_source_IMFRateControl_AddRef,
    media_source_IMFRateControl_Release,
    media_source_IMFRateControl_SetRate,
    media_source_IMFRateControl_GetRate,
};

static HRESULT WINAPI media_source_QueryInterface(IMFMediaSource *iface, REFIID riid, void **out)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);

    TRACE("source %p, riid %s, out %p\n", source, debugstr_guid(riid), out);

    if (IsEqualIID(riid, &IID_IUnknown)
            || IsEqualIID(riid, &IID_IMFMediaEventGenerator)
            || IsEqualIID(riid, &IID_IMFMediaSource))
    {
        IMFMediaSource_AddRef(&source->IMFMediaSource_iface);
        *out = &source->IMFMediaSource_iface;
        return S_OK;
    }

    if (IsEqualIID(riid, &IID_IMFGetService))
    {
        IMFGetService_AddRef(&source->IMFGetService_iface);
        *out = &source->IMFGetService_iface;
        return S_OK;
    }

    FIXME("Unsupported interface %s\n", debugstr_guid(riid));
    *out = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI media_source_AddRef(IMFMediaSource *iface)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);
    ULONG refcount = InterlockedIncrement(&source->refcount);
    TRACE("source %p, refcount %ld\n", source, refcount);
    return refcount;
}

static ULONG WINAPI media_source_Release(IMFMediaSource *iface)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);
    ULONG refcount = InterlockedDecrement(&source->refcount);

    TRACE("source %p, refcount %ld\n", source, refcount);

    if (!refcount)
    {
        IMFMediaSource_Shutdown(iface);

        winedmo_demuxer_destroy(&source->winedmo_demuxer);
        free(source->stream_map);
        free(source->streams);

        IMFMediaEventQueue_Release(source->queue);
        free(source->url);

        source->cs.DebugInfo->Spare[0] = 0;
        DeleteCriticalSection(&source->cs);

        free(source);
    }

    return refcount;
}

static HRESULT WINAPI media_source_GetEvent(IMFMediaSource *iface, DWORD flags, IMFMediaEvent **event)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);
    TRACE("source %p, flags %#lx, event %p\n", source, flags, event);
    return IMFMediaEventQueue_GetEvent(source->queue, flags, event);
}

static HRESULT WINAPI media_source_BeginGetEvent(IMFMediaSource *iface, IMFAsyncCallback *callback, IUnknown *state)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);
    TRACE("source %p, callback %p, state %p\n", source, callback, state);
    return IMFMediaEventQueue_BeginGetEvent(source->queue, callback, state);
}

static HRESULT WINAPI media_source_EndGetEvent(IMFMediaSource *iface, IMFAsyncResult *result,
        IMFMediaEvent **event)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);
    TRACE("source %p, result %p, event %p\n", source, result, event);
    return IMFMediaEventQueue_EndGetEvent(source->queue, result, event);
}

static HRESULT WINAPI media_source_QueueEvent(IMFMediaSource *iface, MediaEventType event_type,
        REFGUID ext_type, HRESULT hr, const PROPVARIANT *value)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);
    TRACE("source %p, event_type %#lx, ext_type %s, hr %#lx, value %p\n", source, event_type,
            debugstr_guid(ext_type), hr, debugstr_propvar(value));
    return IMFMediaEventQueue_QueueEventParamVar(source->queue, event_type, ext_type, hr, value);
}

static HRESULT WINAPI media_source_GetCharacteristics(IMFMediaSource *iface, DWORD *characteristics)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);
    HRESULT hr;

    TRACE("source %p, characteristics %p\n", source, characteristics);

    EnterCriticalSection(&source->cs);
    if (source->state == SOURCE_SHUTDOWN)
        hr = MF_E_SHUTDOWN;
    else
    {
        *characteristics = MFMEDIASOURCE_CAN_SEEK | MFMEDIASOURCE_CAN_PAUSE;
        hr = S_OK;
    }
    LeaveCriticalSection(&source->cs);

    return hr;
}

static HRESULT media_source_create_presentation_descriptor(struct media_source *source, IMFPresentationDescriptor **descriptor)
{
    IMFStreamDescriptor **descriptors;
    HRESULT hr;
    UINT i;

    if (!(descriptors = malloc(source->stream_count * sizeof(*descriptors))))
        return E_OUTOFMEMORY;
    for (i = 0; i < source->stream_count; ++i)
        descriptors[i] = source->streams[i]->descriptor;
    hr = MFCreatePresentationDescriptor(source->stream_count, descriptors, descriptor);
    free(descriptors);

    return hr;
}

static HRESULT WINAPI media_source_CreatePresentationDescriptor(IMFMediaSource *iface, IMFPresentationDescriptor **descriptor)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);
    HRESULT hr;
    UINT i;

    TRACE("source %p, descriptor %p\n", source, descriptor);

    EnterCriticalSection(&source->cs);

    if (source->state == SOURCE_SHUTDOWN)
        hr = MF_E_SHUTDOWN;
    else if (SUCCEEDED(hr = media_source_create_presentation_descriptor(source, descriptor)))
    {
        if (FAILED(hr = IMFPresentationDescriptor_SetString(*descriptor, &MF_PD_MIME_TYPE, source->mime_type)))
            WARN("Failed to set presentation descriptor MF_PD_MIME_TYPE, hr %#lx\n", hr);
        if (FAILED(hr = IMFPresentationDescriptor_SetUINT64(*descriptor, &MF_PD_TOTAL_FILE_SIZE, source->file_size)))
            WARN("Failed to set presentation descriptor MF_PD_TOTAL_FILE_SIZE, hr %#lx\n", hr);
        if (FAILED(hr = IMFPresentationDescriptor_SetUINT64(*descriptor, &MF_PD_DURATION, source->duration)))
            WARN("Failed to set presentation descriptor MF_PD_DURATION, hr %#lx\n", hr);

        for (i = 0; i < source->stream_count; ++i)
        {
            if (!source->streams[i]->active)
                continue;
            if (FAILED(hr = IMFPresentationDescriptor_SelectStream(*descriptor, i)))
                WARN("Failed to select stream %u, hr %#lx\n", i, hr);
        }

        hr = S_OK;
    }

    LeaveCriticalSection(&source->cs);

    return hr;
}

static HRESULT WINAPI media_source_Start(IMFMediaSource *iface, IMFPresentationDescriptor *descriptor, const GUID *format,
        const PROPVARIANT *position)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);
    IUnknown *op;
    HRESULT hr;

    TRACE("source %p, descriptor %p, format %s, position %s\n", source, descriptor,
            debugstr_guid(format), debugstr_propvar(position));

    if (!format)
        format = &GUID_NULL;

    EnterCriticalSection(&source->cs);

    if (source->state == SOURCE_SHUTDOWN)
        hr = MF_E_SHUTDOWN;
    else if (!IsEqualIID(format, &GUID_NULL))
        hr = MF_E_UNSUPPORTED_TIME_FORMAT;
    else if (SUCCEEDED(hr = async_start_params_create(descriptor, format, position, &op)))
    {
        hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, &source->async_start_iface, op);
        IUnknown_Release(op);
    }

    LeaveCriticalSection(&source->cs);

    return hr;
}

static HRESULT WINAPI media_source_Stop(IMFMediaSource *iface)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);
    HRESULT hr;

    TRACE("source %p\n", source);

    EnterCriticalSection(&source->cs);

    if (source->state == SOURCE_SHUTDOWN)
        hr = MF_E_SHUTDOWN;
    else
        hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, &source->async_stop_iface, NULL);

    LeaveCriticalSection(&source->cs);

    return hr;
}

static HRESULT WINAPI media_source_Pause(IMFMediaSource *iface)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);
    HRESULT hr;

    TRACE("source %p\n", source);

    EnterCriticalSection(&source->cs);

    if (source->state == SOURCE_SHUTDOWN)
        hr = MF_E_SHUTDOWN;
    else if (source->state != SOURCE_RUNNING)
        hr = MF_E_INVALID_STATE_TRANSITION;
    else
        hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, &source->async_pause_iface, NULL);

    LeaveCriticalSection(&source->cs);

    return hr;
}

static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface)
{
    struct media_source *source = media_source_from_IMFMediaSource(iface);

    TRACE("source %p\n", source);

    EnterCriticalSection(&source->cs);

    if (source->state == SOURCE_SHUTDOWN)
    {
        LeaveCriticalSection(&source->cs);
        return MF_E_SHUTDOWN;
    }
    source->state = SOURCE_SHUTDOWN;

    IMFMediaEventQueue_QueueEventParamVar(source->queue, MEError, &GUID_NULL, MF_E_SHUTDOWN, NULL);
    IMFMediaEventQueue_Shutdown(source->queue);
    IMFByteStream_Close(source->stream);
    IMFByteStream_Release(source->stream);

    while (source->stream_count--)
    {
        struct media_stream *stream = source->streams[source->stream_count];
        IMFMediaEventQueue_QueueEventParamVar(stream->queue, MEError, &GUID_NULL, MF_E_SHUTDOWN, NULL);
        IMFMediaEventQueue_Shutdown(stream->queue);
        IMFMediaStream_Release(&stream->IMFMediaStream_iface);
    }

    LeaveCriticalSection(&source->cs);

    return S_OK;
}

static const IMFMediaSourceVtbl media_source_vtbl =
{
    media_source_QueryInterface,
    media_source_AddRef,
    media_source_Release,
    media_source_GetEvent,
    media_source_BeginGetEvent,
    media_source_EndGetEvent,
    media_source_QueueEvent,
    media_source_GetCharacteristics,
    media_source_CreatePresentationDescriptor,
    media_source_Start,
    media_source_Stop,
    media_source_Pause,
    media_source_Shutdown,
};

static HRESULT media_type_from_mf_video_format( const MFVIDEOFORMAT *format, IMFMediaType **media_type )
{
    HRESULT hr;

    TRACE("format %p, media_type %p\n", format, media_type);

    if (FAILED(hr = MFCreateVideoMediaType( format, (IMFVideoMediaType **)media_type )) ||
        format->dwSize <= sizeof(*format))
        return hr;

    if (FAILED(IMFMediaType_GetItem(*media_type, &MF_MT_VIDEO_ROTATION, NULL)))
        IMFMediaType_SetUINT32(*media_type, &MF_MT_VIDEO_ROTATION, MFVideoRotationFormat_0);

    return hr;
}

static HRESULT media_type_from_winedmo_format( GUID major, union winedmo_format *format, IMFMediaType **media_type )
{
    TRACE("major %p, format %p, media_type %p\n", &major, format, media_type);

    if (IsEqualGUID( &major, &MFMediaType_Video ))
        return media_type_from_mf_video_format( &format->video, media_type );
    if (IsEqualGUID( &major, &MFMediaType_Audio ))
        return MFCreateAudioMediaType( &format->audio, (IMFAudioMediaType **)media_type );

    FIXME( "Unsupported major type %s\n", debugstr_guid( &major ) );
    return E_NOTIMPL;
}

static HRESULT get_stream_media_type(struct winedmo_demuxer demuxer, UINT index, GUID *major, IMFMediaType **media_type)
{
    union winedmo_format *format;
    NTSTATUS status;
    HRESULT hr;

    TRACE("demuxer %p, index %u, media_type %p\n", &demuxer, index, media_type);

    if ((status = winedmo_demuxer_stream_type(demuxer, index, major, &format)))
    {
        WARN("Failed to get stream %u type, status %#lx\n", index, status);
        return HRESULT_FROM_NT(status);
    }

    hr = media_type ? media_type_from_winedmo_format(*major, format, media_type) : S_OK;
    free(format);
    return hr;
}

static void media_source_init_stream_map(struct media_source *source, UINT stream_count)
{
    int i, n = 0;
    GUID major;

    TRACE("source %p, stream_count %d\n", source, stream_count);

    if (wcscmp(source->mime_type, L"video/mp4"))
    {
        for (i = 0; i < stream_count; i++)
        {
            if (FAILED(get_stream_media_type(source->winedmo_demuxer, i, &major, NULL)))
                continue;
            TRACE("mapping source %p stream %u to demuxer stream %u\n", source, n, i);
            source->stream_map[n++] = i;
        }
        return;
    }

    for (i = stream_count - 1; i >= 0; i--)
    {
        if (FAILED(get_stream_media_type(source->winedmo_demuxer, i, &major, NULL)))
            continue;
        if (IsEqualGUID(&major, &MFMediaType_Audio))
        {
            TRACE("mapping source %p stream %u to demuxer stream %u\n", source, n, i);
            source->stream_map[n++] = i;
        }
    }
    for (i = stream_count - 1; i >= 0; i--)
    {
        if (FAILED(get_stream_media_type(source->winedmo_demuxer, i, &major, NULL)))
            continue;
        if (IsEqualGUID(&major, &MFMediaType_Video))
        {
            TRACE("mapping source %p stream %u to demuxer stream %u\n", source, n, i);
            source->stream_map[n++] = i;
        }
    }
    for (i = stream_count - 1; i >= 0; i--)
    {
        if (FAILED(get_stream_media_type(source->winedmo_demuxer, i, &major, NULL)))
            major = GUID_NULL;
        if (!IsEqualGUID(&major, &MFMediaType_Audio) && !IsEqualGUID(&major, &MFMediaType_Video))
        {
            TRACE("mapping source %p stream %u to demuxer stream %u\n", source, n, i);
            source->stream_map[n++] = i;
        }
    }
}

static HRESULT normalize_mp4_language_code(struct media_source *source, WCHAR *buffer)
{
    static const WCHAR mapping[] = L"aar:aa,abk:ab,ave:ae,afr:af,aka:ak,amh:am,arg:an,"
                                    "ara:ar,asm:as,ava:av,aym:ay,aze:az,bak:ba,bel:be,"
                                    "bul:bg,bih:bh,bis:bi,bam:bm,ben:bn,bod:bo,tib:bo,"
                                    "bre:br,bos:bs,cat:ca,che:ce,cha:ch,cos:co,cre:cr,"
                                    "ces:cs,cze:cs,chu:cu,chv:cv,cym:cy,wel:cy,dan:da,"
                                    "deu:de,ger:de,div:dv,dzo:dz,ewe:ee,ell:el,gre:el,"
                                    "eng:en,epo:eo,spa:es,est:et,eus:eu,baq:eu,fas:fa,"
                                    "per:fa,ful:ff,fin:fi,fij:fj,fao:fo,fra:fr,fre:fr,"
                                    "fry:fy,gle:ga,gla:gd,glg:gl,grn:gn,guj:gu,glv:gv,"
                                    "hau:ha,heb:he,hin:hi,hmo:ho,hrv:hr,hat:ht,hun:hu,"
                                    "hye:hy,arm:hy,her:hz,ina:ia,ind:id,ile:ie,ibo:ig,"
                                    "iii:ii,ipk:ik,ido:io,isl:is,ice:is,ita:it,iku:iu,"
                                    "jpn:ja,jav:jv,kat:ka,geo:ka,kon:kg,kik:ki,kua:kj,"
                                    "kaz:kk,kal:kl,khm:km,kan:kn,kor:ko,kau:kr,kas:ks,"
                                    "kur:ku,kom:kv,cor:kw,kir:ky,lat:la,ltz:lb,lug:lg,"
                                    "lim:li,lin:ln,lao:lo,lit:lt,lub:lu,lav:lv,mlg:mg,"
                                    "mah:mh,mri:mi,mao:mi,mkd:mk,mac:mk,mal:ml,mon:mn,"
                                    "mar:mr,msa:ms,may:ms,mlt:mt,mya:my,bur:my,nau:na,"
                                    "nob:nb,nde:nd,nep:ne,ndo:ng,nld:nl,dut:nl,nno:nn,"
                                    "nor:no,nbl:nr,nav:nv,nya:ny,oci:oc,oji:oj,orm:om,"
                                    "ori:or,oss:os,pan:pa,pli:pi,pol:pl,pus:ps,por:pt,"
                                    "que:qu,roh:rm,run:rn,ron:ro,rum:ro,rus:ru,kin:rw,"
                                    "san:sa,srd:sc,snd:sd,sme:se,sag:sg,sin:si,slk:sk,"
                                    "slo:sk,slv:sl,smo:sm,sna:sn,som:so,sqi:sq,alb:sq,"
                                    "srp:sr,ssw:ss,sot:st,sun:su,swe:sv,swa:sw,tam:ta,"
                                    "tel:te,tgk:tg,tha:th,tir:ti,tuk:tk,tgl:tl,tsn:tn,"
                                    "ton:to,tur:tr,tso:ts,tat:tt,twi:tw,tah:ty,uig:ug,"
                                    "ukr:uk,urd:ur,uzb:uz,ven:ve,vie:vi,vol:vo,wln:wa,"
                                    "wol:wo,xho:xh,yid:yi,yor:yo,zha:za,zho:zh,chi:zh,"
                                    "zul:zu";
    const WCHAR *tmp;
    TRACE("source %p, buffer %s\n", source, debugstr_w(buffer));

    if (wcslen(buffer) != 3) return S_OK;
    if ((tmp = wcsstr(mapping, buffer)) && tmp[3] == ':') lstrcpynW(buffer, tmp + 4, 3);
    if (!tmp) return E_UNEXPECTED;
    return S_OK;
}

static void media_source_init_descriptors(struct media_source *source)
{
    UINT i, last_audio = -1, last_video = -1, first_audio = -1, first_video = -1;

    TRACE("source %p\n", source);

    for (i = 0; i < source->stream_count; i++)
    {
        struct media_stream *stream = source->streams[i];
        UINT exclude = -1;
        WCHAR buffer[512];
        NTSTATUS status;
        GUID major;

        if ((status = winedmo_demuxer_stream_lang(source->winedmo_demuxer, source->stream_map[i], buffer, ARRAY_SIZE(buffer)))
                || (!wcscmp(source->mime_type, L"video/mp4") && FAILED(normalize_mp4_language_code(source, buffer)))
                || FAILED(IMFStreamDescriptor_SetString(stream->descriptor, &MF_SD_LANGUAGE, buffer)))
            WARN("Failed to set stream descriptor language, status %#lx\n", status);
        if ((status = winedmo_demuxer_stream_name(source->winedmo_demuxer, source->stream_map[i], buffer, ARRAY_SIZE(buffer)))
                || FAILED(IMFStreamDescriptor_SetString(stream->descriptor, &MF_SD_STREAM_NAME, buffer)))
            WARN("Failed to set stream descriptor name, status %#lx\n", status);

        if (FAILED(get_stream_media_type(source->winedmo_demuxer, source->stream_map[i], &major, NULL)))
            continue;

        if (IsEqualGUID(&major, &MFMediaType_Audio))
        {
            if (first_audio == -1)
                first_audio = i;
            exclude = last_audio;
            last_audio = i;
        }
        else if (IsEqualGUID(&major, &MFMediaType_Video))
        {
            if (first_video == -1)
                first_video = i;
            exclude = last_video;
            last_video = i;
        }

        if (exclude != -1)
        {
            if (FAILED(IMFStreamDescriptor_SetUINT32(source->streams[exclude]->descriptor, &MF_SD_MUTUALLY_EXCLUSIVE, 1)))
                WARN("Failed to set stream %u MF_SD_MUTUALLY_EXCLUSIVE\n", exclude);
            else if (FAILED(IMFStreamDescriptor_SetUINT32(stream->descriptor, &MF_SD_MUTUALLY_EXCLUSIVE, 1)))
                WARN("Failed to set stream %u MF_SD_MUTUALLY_EXCLUSIVE\n", i);
        }
    }

    if (!wcscmp(source->mime_type, L"video/mp4"))
    {
        if (last_audio != -1)
            source->streams[last_audio]->active = TRUE;
        if (last_video != -1)
            source->streams[last_video]->active = TRUE;
    }
    else
    {
        if (first_audio != -1)
            source->streams[first_audio]->active = TRUE;
        if (first_video != -1)
            source->streams[first_video]->active = TRUE;
    }
}

static HRESULT stream_descriptor_create(UINT32 id, IMFMediaType *media_type, IMFStreamDescriptor **out)
{
    IMFStreamDescriptor *descriptor;
    IMFMediaTypeHandler *handler;
    HRESULT hr;

    TRACE("id %d, media_type %p, out %p\n", id, media_type, out);

    *out = NULL;
    if (FAILED(hr = MFCreateStreamDescriptor(id, 1, &media_type, &descriptor)))
        return hr;

    if (FAILED(hr = IMFStreamDescriptor_GetMediaTypeHandler(descriptor, &handler)))
        IMFStreamDescriptor_Release(descriptor);
    else
    {
        if (SUCCEEDED(hr = IMFMediaTypeHandler_SetCurrentMediaType(handler, media_type)))
            *out = descriptor;
        IMFMediaTypeHandler_Release(handler);
    }

    return hr;
}

static NTSTATUS CDECL media_source_seek_cb( struct winedmo_stream *stream, UINT64 *pos )
{
    struct media_source *source = CONTAINING_RECORD(stream, struct media_source, winedmo_stream);
    TRACE("stream %p, pos %p\n", stream, pos);

    if (FAILED(IMFByteStream_Seek(source->stream, msoBegin, *pos, 0, pos)))
        return STATUS_UNSUCCESSFUL;
    return STATUS_SUCCESS;
}

static NTSTATUS CDECL media_source_read_cb(struct winedmo_stream *stream, BYTE *buffer, ULONG *size)
{
    struct media_source *source = CONTAINING_RECORD(stream, struct media_source, winedmo_stream);
    TRACE("stream %p, buffer %p, size %p\n", stream, buffer, size);

    if (FAILED(IMFByteStream_Read(source->stream, buffer, *size, size)))
        return STATUS_UNSUCCESSFUL;
    return STATUS_SUCCESS;
}

static HRESULT media_source_async_create(struct media_source *source, IMFAsyncResult *result)
{
    IUnknown *state = IMFAsyncResult_GetStateNoAddRef(result);
    UINT i, stream_count;
    NTSTATUS status;
    HRESULT hr;

    TRACE("source %p, result %p\n", source, result);

    if (FAILED(hr = IMFByteStream_GetLength(source->stream, &source->file_size)))
    {
        WARN("Failed to get byte stream length, hr %#lx\n", hr);
        source->file_size = -1;
    }
    if (FAILED(hr = IMFByteStream_SetCurrentPosition(source->stream, 0)))
    {
        WARN("Failed to set byte stream position, hr %#lx\n", hr);
        hr = S_OK;
    }

    source->winedmo_stream.p_seek = media_source_seek_cb;
    source->winedmo_stream.p_read = media_source_read_cb;

    if ((status = winedmo_demuxer_create(source->url, &source->winedmo_stream, source->file_size, &source->duration,
            &stream_count, source->mime_type, &source->winedmo_demuxer)))
    {
        WARN("Failed to create demuxer, status %#lx\n", status);
        hr = HRESULT_FROM_NT(status);
        goto done;
    }

    if (!(source->stream_map = calloc(stream_count, sizeof(*source->stream_map)))
            || !(source->streams = calloc(stream_count, sizeof(*source->streams))))
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    media_source_init_stream_map(source, stream_count);

    for (i = 0; SUCCEEDED(hr) && i < stream_count; ++i)
    {
        IMFStreamDescriptor *descriptor;
        IMFMediaType *media_type;
        GUID major;

        if (FAILED(hr = get_stream_media_type(source->winedmo_demuxer, source->stream_map[i], &major, &media_type)))
            continue;
        if (SUCCEEDED(hr = stream_descriptor_create(i + 1, media_type, &descriptor)))
        {
            if (SUCCEEDED(hr = media_stream_create(&source->IMFMediaSource_iface, descriptor, &source->streams[i])))
                source->stream_count++;
            IMFStreamDescriptor_Release(descriptor);
        }
        IMFMediaType_Release(media_type);
    }

    media_source_init_descriptors(source);

done:
    IMFAsyncResult_SetStatus(result, hr);
    return MFInvokeCallback((IMFAsyncResult *)state);
}

DEFINE_MF_ASYNC_CALLBACK(media_source, async_create, IMFMediaSource_iface)

static WCHAR *get_byte_stream_url(IMFByteStream *stream, const WCHAR *url)
{
    IMFAttributes *attributes;
    WCHAR buffer[MAX_PATH];
    UINT32 size;
    HRESULT hr;

    TRACE("stream %p, url %s\n", stream, debugstr_w(url));

    if (SUCCEEDED(hr = IMFByteStream_QueryInterface(stream, &IID_IMFAttributes, (void **)&attributes)))
    {
        if (FAILED(hr = IMFAttributes_GetString(attributes, &MF_BYTESTREAM_ORIGIN_NAME,
                buffer, ARRAY_SIZE(buffer), &size)))
            WARN("Failed to get MF_BYTESTREAM_ORIGIN_NAME got size %#x, hr %#lx\n", size, hr);
        else
            url = buffer;
        IMFAttributes_Release(attributes);
    }

    return url ? wcsdup(url) : NULL;
}

static HRESULT media_source_create(const WCHAR *url, IMFByteStream *stream, IMFMediaSource **out)
{
    struct media_source *source;
    HRESULT hr;

    TRACE("url %s, stream %p, out %p\n", debugstr_w(url), stream, out);

    if (!(source = calloc(1, sizeof(*source))))
        return E_OUTOFMEMORY;
    source->IMFMediaSource_iface.lpVtbl = &media_source_vtbl;
    source->IMFGetService_iface.lpVtbl = &media_source_IMFGetService_vtbl;
    source->IMFRateSupport_iface.lpVtbl = &media_source_IMFRateSupport_vtbl;
    source->IMFRateControl_iface.lpVtbl = &media_source_IMFRateControl_vtbl;
    source->async_create_iface.lpVtbl = &media_source_async_create_vtbl;
    source->async_start_iface.lpVtbl = &media_source_async_start_vtbl;
    source->async_stop_iface.lpVtbl = &media_source_async_stop_vtbl;
    source->async_pause_iface.lpVtbl = &media_source_async_pause_vtbl;
    source->async_read_iface.lpVtbl = &media_source_async_read_vtbl;
    source->async_set_rate_iface.lpVtbl = &media_source_async_set_rate_vtbl;
    source->refcount = 1;

    if (FAILED(hr = MFCreateEventQueue(&source->queue)))
    {
        free(source);
        return hr;
    }

    source->url = get_byte_stream_url(stream, url);
    IMFByteStream_AddRef((source->stream = stream));

    source->rate = 1.0f;
    InitializeCriticalSectionEx(&source->cs, 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO);
    source->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": cs");

    *out = &source->IMFMediaSource_iface;
    TRACE("created source %p\n", source);
    return S_OK;
}

struct byte_stream_handler
{
    IMFByteStreamHandler IMFByteStreamHandler_iface;
    LONG refcount;
};

static struct byte_stream_handler *byte_stream_handler_from_IMFByteStreamHandler(IMFByteStreamHandler *iface)
{
    return CONTAINING_RECORD(iface, struct byte_stream_handler, IMFByteStreamHandler_iface);
}

static HRESULT WINAPI byte_stream_handler_QueryInterface(IMFByteStreamHandler *iface, REFIID riid, void **out)
{
    struct byte_stream_handler *handler = byte_stream_handler_from_IMFByteStreamHandler(iface);

    TRACE("handler %p, riid %s, out %p\n", handler, debugstr_guid(riid), out);

    if (IsEqualIID(riid, &IID_IUnknown)
            || IsEqualIID(riid, &IID_IMFByteStreamHandler))
    {
        IMFByteStreamHandler_AddRef(&handler->IMFByteStreamHandler_iface);
        *out = &handler->IMFByteStreamHandler_iface;
        return S_OK;
    }

    WARN("Unsupported %s\n", debugstr_guid(riid));
    *out = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI byte_stream_handler_AddRef(IMFByteStreamHandler *iface)
{
    struct byte_stream_handler *handler = byte_stream_handler_from_IMFByteStreamHandler(iface);
    ULONG refcount = InterlockedIncrement(&handler->refcount);
    TRACE("handler %p, refcount %ld\n", handler, refcount);
    return refcount;
}

static ULONG WINAPI byte_stream_handler_Release(IMFByteStreamHandler *iface)
{
    struct byte_stream_handler *handler = byte_stream_handler_from_IMFByteStreamHandler(iface);
    ULONG refcount = InterlockedDecrement(&handler->refcount);
    TRACE("handler %p, refcount %ld\n", handler, refcount);
    if (!refcount)
        free(handler);
    return refcount;
}

static HRESULT WINAPI byte_stream_handler_BeginCreateObject(IMFByteStreamHandler *iface, IMFByteStream *stream, const WCHAR *url, DWORD flags,
        IPropertyStore *props, IUnknown **cookie, IMFAsyncCallback *callback, IUnknown *state)
{
    struct byte_stream_handler *handler = byte_stream_handler_from_IMFByteStreamHandler(iface);
    IMFAsyncResult *result;
    IMFMediaSource *source;
    HRESULT hr;
    DWORD caps;

    TRACE("handler %p, stream %p, url %s, flags %#lx, props %p, cookie %p, callback %p, state %p\n",
            handler, stream, debugstr_w(url), flags, props, cookie, callback, state);

    if (cookie)
        *cookie = NULL;
    if (!stream)
        return E_INVALIDARG;
    if (flags != MF_RESOLUTION_MEDIASOURCE)
        FIXME("Unimplemented flags %#lx\n", flags);

    if (FAILED(hr = IMFByteStream_GetCapabilities(stream, &caps)))
        return hr;
    if (!(caps & MFBYTESTREAM_IS_SEEKABLE))
    {
        FIXME("Non-seekable bytestreams not supported\n");
        return MF_E_BYTESTREAM_NOT_SEEKABLE;
    }

    if (FAILED(hr = media_source_create(url, stream, &source)))
        return hr;
    if (SUCCEEDED(hr = MFCreateAsyncResult((IUnknown *)source, callback, state, &result)))
    {
        struct media_source *media_source = media_source_from_IMFMediaSource(source);
        hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_IO, &media_source->async_create_iface, (IUnknown *)result);
        IMFAsyncResult_Release(result);
    }
    IMFMediaSource_Release(source);

    return hr;
}

static HRESULT WINAPI byte_stream_handler_EndCreateObject(IMFByteStreamHandler *iface, IMFAsyncResult *result,
        MF_OBJECT_TYPE *type, IUnknown **object)
{
    struct byte_stream_handler *handler = byte_stream_handler_from_IMFByteStreamHandler(iface);
    HRESULT hr;

    TRACE("handler %p, result %p, type %p, object %p\n", handler, result, type, object);

    *object = NULL;
    *type = MF_OBJECT_INVALID;

    if (SUCCEEDED(hr = IMFAsyncResult_GetStatus(result)))
    {
        hr = IMFAsyncResult_GetObject(result, object);
        *type = MF_OBJECT_MEDIASOURCE;
    }

    return hr;
}

static HRESULT WINAPI byte_stream_handler_CancelObjectCreation(IMFByteStreamHandler *iface, IUnknown *cookie)
{
    struct byte_stream_handler *handler = byte_stream_handler_from_IMFByteStreamHandler(iface);
    FIXME("handler %p, cookie %p, stub!\n", handler, cookie);
    return E_NOTIMPL;
}

static HRESULT WINAPI byte_stream_handler_GetMaxNumberOfBytesRequiredForResolution(IMFByteStreamHandler *iface, QWORD *bytes)
{
    struct byte_stream_handler *handler = byte_stream_handler_from_IMFByteStreamHandler(iface);
    FIXME("handler %p, bytes %p, stub!\n", handler, bytes);
    return E_NOTIMPL;
}

static const IMFByteStreamHandlerVtbl byte_stream_handler_vtbl =
{
    byte_stream_handler_QueryInterface,
    byte_stream_handler_AddRef,
    byte_stream_handler_Release,
    byte_stream_handler_BeginCreateObject,
    byte_stream_handler_EndCreateObject,
    byte_stream_handler_CancelObjectCreation,
    byte_stream_handler_GetMaxNumberOfBytesRequiredForResolution,
};

static HRESULT byte_stream_plugin_create(IUnknown *outer, REFIID riid, void **out)
{
    struct byte_stream_handler *handler;
    HRESULT hr;

    TRACE("outer %p, riid %s, out %p\n", outer, debugstr_guid(riid), out);

    if (outer)
        return CLASS_E_NOAGGREGATION;
    if (!(handler = calloc(1, sizeof(*handler))))
        return E_OUTOFMEMORY;
    handler->IMFByteStreamHandler_iface.lpVtbl = &byte_stream_handler_vtbl;
    handler->refcount = 1;
    TRACE("created %p\n", handler);

    hr = IMFByteStreamHandler_QueryInterface(&handler->IMFByteStreamHandler_iface, riid, out);
    IMFByteStreamHandler_Release(&handler->IMFByteStreamHandler_iface);
    return hr;
}

static BOOL use_gst_byte_stream_handler(void)
{
    BOOL result;
    DWORD size = sizeof(result);

    /* @@ Wine registry key: HKCU\Software\Wine\MediaFoundation */
    if (!RegGetValueW( HKEY_CURRENT_USER, L"Software\\Wine\\MediaFoundation", L"DisableGstByteStreamHandler",
                       RRF_RT_REG_DWORD, NULL, &result, &size ))
        return !result;

    return TRUE;
}

static HRESULT WINAPI asf_byte_stream_plugin_factory_CreateInstance(IClassFactory *iface,
        IUnknown *outer, REFIID riid, void **out)
{
    NTSTATUS status;

    if ((status = winedmo_demuxer_check("video/x-ms-asf")) || use_gst_byte_stream_handler())
    {
        static const GUID CLSID_GStreamerByteStreamHandler = {0x317df618,0x5e5a,0x468a,{0x9f,0x15,0xd8,0x27,0xa9,0xa0,0x81,0x62}};
        if (status) WARN("Unsupported demuxer, status %#lx.\n", status);
        return CoCreateInstance(&CLSID_GStreamerByteStreamHandler, outer, CLSCTX_INPROC_SERVER, riid, out);
    }

    return byte_stream_plugin_create(outer, riid, out);
}

static const IClassFactoryVtbl asf_byte_stream_plugin_factory_vtbl =
{
    class_factory_QueryInterface,
    class_factory_AddRef,
    class_factory_Release,
    asf_byte_stream_plugin_factory_CreateInstance,
    class_factory_LockServer,
};

IClassFactory asf_byte_stream_plugin_factory = {&asf_byte_stream_plugin_factory_vtbl};

static HRESULT WINAPI avi_byte_stream_plugin_factory_CreateInstance(IClassFactory *iface,
        IUnknown *outer, REFIID riid, void **out)
{
    NTSTATUS status;

    if ((status = winedmo_demuxer_check("video/avi")) || use_gst_byte_stream_handler())
    {
        static const GUID CLSID_GStreamerByteStreamHandler = {0x317df618,0x5e5a,0x468a,{0x9f,0x15,0xd8,0x27,0xa9,0xa0,0x81,0x62}};
        if (status) WARN("Unsupported demuxer, status %#lx.\n", status);
        return CoCreateInstance(&CLSID_GStreamerByteStreamHandler, outer, CLSCTX_INPROC_SERVER, riid, out);
    }

    return byte_stream_plugin_create(outer, riid, out);
}

static const IClassFactoryVtbl avi_byte_stream_plugin_factory_vtbl =
{
    class_factory_QueryInterface,
    class_factory_AddRef,
    class_factory_Release,
    avi_byte_stream_plugin_factory_CreateInstance,
    class_factory_LockServer,
};

IClassFactory avi_byte_stream_plugin_factory = {&avi_byte_stream_plugin_factory_vtbl};

static HRESULT WINAPI mpeg4_byte_stream_plugin_factory_CreateInstance(IClassFactory *iface,
        IUnknown *outer, REFIID riid, void **out)
{
    NTSTATUS status;

    if ((status = winedmo_demuxer_check("video/mp4")) || use_gst_byte_stream_handler())
    {
        static const GUID CLSID_GStreamerByteStreamHandler = {0x317df618,0x5e5a,0x468a,{0x9f,0x15,0xd8,0x27,0xa9,0xa0,0x81,0x62}};
        if (status) WARN("Unsupported demuxer, status %#lx.\n", status);
        return CoCreateInstance(&CLSID_GStreamerByteStreamHandler, outer, CLSCTX_INPROC_SERVER, riid, out);
    }

    return byte_stream_plugin_create(outer, riid, out);
}

static const IClassFactoryVtbl mpeg4_byte_stream_plugin_factory_vtbl =
{
    class_factory_QueryInterface,
    class_factory_AddRef,
    class_factory_Release,
    mpeg4_byte_stream_plugin_factory_CreateInstance,
    class_factory_LockServer,
};

IClassFactory mpeg4_byte_stream_plugin_factory = {&mpeg4_byte_stream_plugin_factory_vtbl};

static HRESULT WINAPI wav_byte_stream_plugin_factory_CreateInstance(IClassFactory *iface,
        IUnknown *outer, REFIID riid, void **out)
{
    NTSTATUS status;

    if ((status = winedmo_demuxer_check("audio/wav")) || use_gst_byte_stream_handler())
    {
        static const GUID CLSID_GStreamerByteStreamHandler = {0x317df618,0x5e5a,0x468a,{0x9f,0x15,0xd8,0x27,0xa9,0xa0,0x81,0x62}};
        if (status) WARN("Unsupported demuxer, status %#lx.\n", status);
        return CoCreateInstance(&CLSID_GStreamerByteStreamHandler, outer, CLSCTX_INPROC_SERVER, riid, out);
    }

    return byte_stream_plugin_create(outer, riid, out);
}

static const IClassFactoryVtbl wav_byte_stream_plugin_factory_vtbl =
{
    class_factory_QueryInterface,
    class_factory_AddRef,
    class_factory_Release,
    wav_byte_stream_plugin_factory_CreateInstance,
    class_factory_LockServer,
};

IClassFactory wav_byte_stream_plugin_factory = {&wav_byte_stream_plugin_factory_vtbl};

static HRESULT WINAPI mp3_byte_stream_plugin_factory_CreateInstance(IClassFactory *iface,
        IUnknown *outer, REFIID riid, void **out)
{
    NTSTATUS status;

    if ((status = winedmo_demuxer_check("audio/mp3")) || use_gst_byte_stream_handler())
    {
        static const GUID CLSID_GStreamerByteStreamHandler = {0x317df618,0x5e5a,0x468a,{0x9f,0x15,0xd8,0x27,0xa9,0xa0,0x81,0x62}};
        if (status) WARN("Unsupported demuxer, status %#lx.\n", status);
        return CoCreateInstance(&CLSID_GStreamerByteStreamHandler, outer, CLSCTX_INPROC_SERVER, riid, out);
    }

    return byte_stream_plugin_create(outer, riid, out);
}

static const IClassFactoryVtbl mp3_byte_stream_plugin_factory_vtbl =
{
    class_factory_QueryInterface,
    class_factory_AddRef,
    class_factory_Release,
    mp3_byte_stream_plugin_factory_CreateInstance,
    class_factory_LockServer,
};

IClassFactory mp3_byte_stream_plugin_factory = {&mp3_byte_stream_plugin_factory_vtbl};
