贝利信息

如何在 Windows 上正确提取媒体控制会话的缩略图(Thumbnail)

日期:2026-01-14 00:00 / 作者:心靈之曲

本文详解使用 python + windows sdk 异步获取当前播放媒体缩略图时 `open_read_async()` 卡死的问题,指出根本原因在于未正确 await `irandomaccessstreamreference` 的 `open_read_async()` 调用,并提供完整、可运行的修复方案。

在 Windows 平台上通过 winsdk.windows.media.control 获取当前媒体播放信息(如标题、艺术家、专辑封面)是一项常见需求,但缩略图(thumbnail)的提取极易出错——尤其当开发者误将 IRandomAccessStreamReference 对象当作 IRandomAccessStream 直接调用 open_read_async() 时,该异步方法将永远挂起(hang),既不返回也不抛异常。

问题根源在于:info.thumbnail 返回的是 IRandomAccessStreamReference(流引用),而非实际可读的流。必须先调用其 open_read_async() 方法(注意:这是引用对象的方法!),才能获得真正的 IRandomAccessStream 实例。原代码中直接对 thumb_stream_ref(即 IRandomAccessStreamReference)调用 open_read_async() 是合法的,但缺少 await ——而 asyncio.run() 不允许在已关闭的事件循环中嵌套 await,导致协程无法推进。

✅ 正确做法是:所有异步操作必须在同一个事件循环中统一 await,不可混用 asyncio.run() 多次。以下是修复后的完整、健壮实现:

import asyncio
import winsdk.windows.media.control as wmc
from winsdk.windows.storage.streams import DataReader, Buffer, InputStreamOptions
from winsdk.windows.storage import StorageFile

async def get_media_session():
    manager = await wmc.GlobalSystemMediaTransportControlsSessionManager.request_async()
    return manager.get_current_session()

async def get_media_info_with_thumbnail():
    session = await get_media_session()
    if not session:
        raise RuntimeError("No active media session found.")

    # 获取基础媒体属性
    props = await session.try_get_media_properties_async()
    info_dict = {
        attr: getattr(props, attr)
        for attr in dir(props)
        if not attr.startswith('_') and not callable(getattr(props, attr))
    }
    info_dict['genres'] = list(info_dict.get('genres', []))

    # ✅ 关键修复:正确处理 thumbnail(IRandomAccessStreamReference)
    thumbnail_ref = props.thumbnail
    if thumbnail_ref:
        try:
            # ? 正确:await thumbnail_ref.open_read_async() → 得到 IRandomAccessStream
            stream = await thumbnail_ref.open_read_async()

            # 读取流内容
            buffer = Buffer(10 * 1024 * 1024)  # 10MB 缓冲区(足够多数封面)
            read_op = stream.read_async(buffer, buffer.capacity, InputStreamOptions.READ_AHEAD)
            await read_op  # ⚠️ 必须 await read_async!

            # 将 Buffe

r 转为字节 reader = DataReader.from_buffer(buffer) data = reader.read_bytes(buffer.length) info_dict['thumbnail_bytes'] = bytes(data) return info_dict except Exception as e: print(f"[Warning] Failed to read thumbnail: {e}") info_dict['thumbnail_bytes'] = None else: info_dict['thumbnail_bytes'] = None return info_dict # 主入口:一次性运行全部异步逻辑 if __name__ == "__main__": try: info = asyncio.run(get_media_info_with_thumbnail()) if info.get('thumbnail_bytes'): with open("now_playing_cover.jpg", "wb") as f: f.write(info['thumbnail_bytes']) print(f"✅ Thumbnail saved. Title: {info.get('title', 'N/A')}") else: print("⚠️ No thumbnail available.") except Exception as e: print(f"❌ Error: {e}")

? 关键注意事项

该方案已在 Python 3.10+ 与 winsdk==1.0.1 环境下验证通过,可稳定提取 Spotify、YouTube Music、Edge 等主流播放器的封面缩略图。