diff --git a/.gitignore b/.gitignore index a5846a9..6ff3760 100644 --- a/.gitignore +++ b/.gitignore @@ -1,77 +1,8 @@ -# This file is used to ignore files which are generated -# ---------------------------------------------------------------------------- +# Common build folder +[Bb]uild/ -*~ -*.autosave -*.a -*.core -*.moc -*.o -*.obj -*.orig -*.rej -*.so -*.so.* -*_pch.h.cpp -*_resource.rc -*.qm -.#* -*.*# -core -!core/ -tags -.DS_Store -.directory -*.debug -Makefile* -*.prl -*.app -moc_*.cpp -ui_*.h -qrc_*.cpp -Thumbs.db -*.res -*.rc -/.qmake.cache -/.qmake.stash +# IDE folder +.vscode/ -# qtcreator generated files -*.pro.user* - -# xemacs temporary files -*.flc - -# Vim temporary files -.*.swp - -# Visual Studio generated files -*.ib_pdb_index -*.idb -*.ilk -*.pdb -*.sln -*.suo -*.vcproj -*vcproj.*.*.user -*.ncb -*.sdf -*.opensdf -*.vcxproj -*vcxproj.* - -# MinGW generated files -*.Debug -*.Release - -# Python byte code -*.pyc - -# Binaries -# -------- -*.dll -*.exe - -# User Config -# ----------- -*.user -*.user.* +# User config file +CMakeLists.txt.user* diff --git a/CMakeLists.txt b/CMakeLists.txt index c111f98..0ffe3e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,22 +24,15 @@ set (PMUSIC_CPP_FILES main.cpp mainwindow.cpp seekableslider.cpp - playlistmodel.cpp + playlistmanager.cpp singleapplicationmanager.cpp - - qt/qplaylistfileparser.cpp - qt/qmediaplaylist.cpp ) set (PMUSIC_HEADER_FILES mainwindow.h seekableslider.h - playlistmodel.h + playlistmanager.h singleapplicationmanager.h - - qt/qplaylistfileparser_p.h - qt/qmediaplaylist.h - qt/qmediaplaylist_p.h ) set (PMUSIC_UI_FILES @@ -60,10 +53,6 @@ add_executable(${EXE_NAME} ${PMUSIC_UI_FILES} resources.qrc - # 3rd party code - FlacPic.h - ID3v2Pic.h - ${PMUSIC_QM_FILES} ) diff --git a/FlacPic.h b/FlacPic.h deleted file mode 100755 index 148e858..0000000 --- a/FlacPic.h +++ /dev/null @@ -1,263 +0,0 @@ -/* -FLAC标签图片提取库 Ver 1.0 -从FLAC文件中稳定、快捷、高效、便捷地提取出图片数据 -支持BMP、JPEG、PNG、GIF图片格式 -可将图片数据提取到文件或内存中,并能安全地释放内存 -使用方式与ID3v2版本相同 -ShadowPower 于2014/8/1 夜间 -*/ - -#ifndef _ShadowPower_FLACPIC___ -#define _ShadowPower_FLACPIC___ -#define _CRT_SECURE_NO_WARNINGS -#ifndef NULL -#define NULL 0 -#endif -#include -#include -#include -#include - -typedef unsigned char byte; - -namespace spFLAC { - //Flac元数据块头部结构体定义 - struct FlacMetadataBlockHeader - { - byte flag; //标志位,高1位:是否为最后一个数据块,低7位:数据块类型 - byte length[3]; //数据块长度,不含数据块头部 - }; - - byte *pPicData = 0; //指向图片数据的指针 - int picLength = 0; //存放图片数据长度 - char picFormat[4] = {}; //存放图片数据的格式(扩展名) - - //检测图片格式,参数1:数据,返回值:是否成功(不是图片则失败) - bool verificationPictureFormat(char *data) - { - //支持格式:JPEG/PNG/BMP/GIF - byte jpeg[2] = { 0xff, 0xd8 }; - byte png[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }; - byte gif[6] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }; - byte gif2[6] = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }; - byte bmp[2] = { 0x42, 0x4d }; - memset(&picFormat, 0, 4); - if (memcmp(data, &jpeg, 2) == 0) - { - strcpy(picFormat, "jpg"); - } - else if (memcmp(data, &png, 8) == 0) - { - strcpy(picFormat, "png"); - } - else if (memcmp(data, &gif, 6) == 0 || memcmp(data, &gif2, 6) == 0) - { - strcpy(picFormat, "gif"); - } - else if (memcmp(data, &bmp, 2) == 0) - { - strcpy(picFormat, "bmp"); - } - else - { - return false; - } - - return true; - } - - //安全释放内存 - void freePictureData() - { - if (pPicData) - { - delete pPicData; - } - pPicData = 0; - picLength = 0; - memset(&picFormat, 0, 4); - } - - //将图片提取到内存,参数1:文件路径,成功返回true - bool loadPictureData(const char *inFilePath) - { - freePictureData(); - FILE *fp = NULL; - fp = fopen(inFilePath, "rb"); - if (!fp) //如果打开失败 - { - fp = NULL; - return false; - } - fseek(fp, 0, SEEK_SET); //设文件流指针到文件头部 - byte magic[4] = {}; //存放校验数据 - memset(&magic, 0, 4); - fread(&magic, 4, 1, fp); //读入校验数据 - byte fLaC[4] = { 0x66, 0x4c, 0x61, 0x43 }; - if (memcmp(&magic, &fLaC, 4) == 0) - { - //数据校验正确,文件类型为Flac - FlacMetadataBlockHeader fmbh; //创建Flac元数据块头部结构体 - memset(&fmbh, 0, 4); //清空内存 - fread(&fmbh, 4, 1, fp); //读入头部数据 - //计算数据块长度,不含头部 - int blockLength = fmbh.length[0] * 0x10000 + fmbh.length[1] * 0x100 + fmbh.length[2]; - int loopCount = 0; //循环计数,防死 - while ((fmbh.flag & 0x7f) != 6) - { - //如果数据类型不是图片,此处循环执行 - loopCount++; - if (loopCount > 40) - { - //循环40次没有遇到末尾就直接停止 - fclose(fp); - fp = NULL; - return false; //可能文件不正常 - } - fseek(fp, blockLength, SEEK_CUR); //跳过数据块 - if ((fmbh.flag & 0x80) == 0x80) - { - //已经是最后一个数据块了,仍然不是图片 - fclose(fp); - fp = NULL; - return false; //没有找到图片数据 - } - //取得下一数据块头部 - memset(&fmbh, 0, 4); //清空内存 - fread(&fmbh, 4, 1, fp); //读入头部数据 - blockLength = fmbh.length[0] * 0x10000 + fmbh.length[1] * 0x100 + fmbh.length[2];//计算数据块长度 - } - //此时已到图片数据块 - - int nonPicDataLength = 0; //非图片数据长度 - fseek(fp, 4, SEEK_CUR); //信仰之跃 - nonPicDataLength += 4; - char nextJumpLength[4]; //下次要跳的长度 - fread(&nextJumpLength, 4, 1, fp); //读取安全跳跃距离 - nonPicDataLength += 4; - int jumpLength = nextJumpLength[0] * 0x1000000 + nextJumpLength[1] * 0x10000 + nextJumpLength[2] * 0x100 + nextJumpLength[3];//计算数据块长度 - fseek(fp, jumpLength, SEEK_CUR); //Let's Jump!! - nonPicDataLength += jumpLength; - fread(&nextJumpLength, 4, 1, fp); - nonPicDataLength += 4; - jumpLength = nextJumpLength[0] * 0x1000000 + nextJumpLength[1] * 0x10000 + nextJumpLength[2] * 0x100 + nextJumpLength[3]; - fseek(fp, jumpLength, SEEK_CUR); //Let's Jump too!! - nonPicDataLength += jumpLength; - fseek(fp, 20, SEEK_CUR); //信仰之跃 - nonPicDataLength += 20; - - //非主流情况检测+获得文件格式 - char tempData[20] = {}; - memset(tempData, 0, 20); - fread(&tempData, 8, 1, fp); - fseek(fp, -8, SEEK_CUR); //回到原位 - //判断40次,一位一位跳到文件头 - bool ok = false; //是否正确识别出文件头 - for (int i = 0; i < 40; i++) - { - //校验文件头 - if (verificationPictureFormat(tempData)) - { - ok = true; - break; - } - else - { - //如果校验失败尝试继续向后校验 - fseek(fp, 1, SEEK_CUR); - nonPicDataLength++; - fread(&tempData, 8, 1, fp); - fseek(fp, -8, SEEK_CUR); - } - } - - if (!ok) - { - fclose(fp); - fp = NULL; - freePictureData(); - return false; //无法识别的数据 - } - - //-----抵达图片数据区----- - picLength = blockLength - nonPicDataLength; //计算图片数据长度 - pPicData = new byte[picLength]; //动态分配图片数据内存空间 - memset(pPicData, 0, picLength); //清空图片数据内存 - fread(pPicData, picLength, 1, fp); //得到图片数据 - //------------------------ - fclose(fp); //操作已完成,关闭文件。 - } - else - { - //校验失败,不是Flac - fclose(fp); - fp = NULL; - freePictureData(); - return false; - } - return true; - } - - //取得图片数据的长度 - int getPictureLength() - { - return picLength; - } - - //取得指向图片数据的指针 - byte *getPictureDataPtr() - { - return pPicData; - } - - //取得图片数据的扩展名(指针) - char *getPictureFormat() - { - return picFormat; - } - - bool writePictureDataToFile(const char *outFilePath) - { - FILE *fp = NULL; - if (picLength > 0) - { - fp = fopen(outFilePath, "wb"); //打开目标文件 - if (fp) //打开成功 - { - fwrite(pPicData, picLength, 1, fp); //写入文件 - fclose(fp); //关闭 - return true; - } - else - { - return false; //文件打开失败 - } - } - else - { - return false; //没有图像数据 - } - } - - //提取图片文件,参数1:输入文件,参数2:输出文件,返回值:是否成功 - bool extractPicture(const char *inFilePath, const char *outFilePath) - { - if (loadPictureData(inFilePath)) //如果取得图片数据成功 - { - if (writePictureDataToFile(outFilePath)) - { - return true; //文件写出成功 - } - else - { - return false; //文件写出失败 - } - } - else - { - return false; //无图片数据 - } - freePictureData(); - } -} -#endif \ No newline at end of file diff --git a/ID3v2Pic.h b/ID3v2Pic.h deleted file mode 100755 index c86910c..0000000 --- a/ID3v2Pic.h +++ /dev/null @@ -1,441 +0,0 @@ -/* -ID3v2标签图片提取库 Ver 1.0 -支持ID3v2所有版本 -从ID3v2标签中稳定、快捷、高效、便捷地提取出图片数据 -支持BMP、JPEG、PNG、GIF图片格式 -可将图片数据提取到文件或内存中,并能安全地释放内存 -ShadowPower 于2014/8/1 上午 -*/ - -#ifndef _ShadowPower_ID3V2PIC___ -#define _ShadowPower_ID3V2PIC___ -#define _CRT_SECURE_NO_WARNINGS -#ifndef NULL -#define NULL 0 -#endif -#include -#include -#include -#include - -typedef unsigned char byte; - -namespace spID3 { - //ID3v2标签头部结构体定义 - struct ID3V2Header - { - char identi[3];//ID3头部校验,必须为“ID3”否则认为不存在ID3标签 - byte major; //ID3版本号,3是ID3v2.3,4是ID3v2.4,以此类推 - byte revsion; //ID3副版本号,此版本为00 - byte flags; //标志位 - byte size[4]; //标签大小,不含标签头的10个字节 - }; - - //ID3v2标签帧头部结构体定义 - struct ID3V2FrameHeader - { - char FrameId[4];//标识符,用于描述此标签帧的内容类型 - byte size[4]; //标签帧的大小,不含标签头的10个字节 - byte flags[2]; //标志位 - }; - - struct ID3V22FrameHeader - { - char FrameId[3];//标识符,用于描述此标签帧的内容类型 - byte size[3]; //标签帧的大小,不含标签头的6个字节 - }; - - byte *pPicData = 0; //指向图片数据的指针 - int picLength = 0; //存放图片数据长度 - char picFormat[4] = {}; //存放图片数据的格式(扩展名) - - // ID3V2.3 & ID3V2.4 帧长度获取 - inline int _frameLength34(ID3V2FrameHeader* fh, byte majorVersion) { - if (!fh || majorVersion < 3) { - return 0; - } - if (majorVersion == 3) { - return fh->size[0] * 0x1000000 + fh->size[1] * 0x10000 + fh->size[2] * 0x100 + fh->size[3]; - } - return (fh->size[0] & 0x7f) * 0x200000 + (fh->size[1] & 0x7f) * 0x4000 + (fh->size[2] & 0x7f) * 0x80 + (fh->size[3] & 0x7f); - } - - //检测图片格式,参数1:数据,返回值:是否成功(不是图片则失败) - bool verificationPictureFormat(char *data) - { - //支持格式:JPEG/PNG/BMP/GIF - byte jpeg[2] = { 0xff, 0xd8 }; - byte png[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }; - byte gif[6] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }; - byte gif2[6] = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }; - byte bmp[2] = { 0x42, 0x4d }; - memset(&picFormat, 0, 4); - if (memcmp(data, &jpeg, 2) == 0) - { - strcpy(picFormat, "jpg"); - } - else if (memcmp(data, &png, 8) == 0) - { - strcpy(picFormat, "png"); - } - else if (memcmp(data, &gif, 6) == 0 || memcmp(data, &gif2, 6) == 0) - { - strcpy(picFormat, "gif"); - } - else if (memcmp(data, &bmp, 2) == 0) - { - strcpy(picFormat, "bmp"); - } - else - { - return false; - } - - return true; - } - - //安全释放内存 - void freePictureData() - { - if (pPicData) - { - delete pPicData; - } - pPicData = 0; - picLength = 0; - memset(&picFormat, 0, 4); - } - - //将图片提取到内存,参数1:文件路径,成功返回true - bool loadPictureData(const char *inFilePath) - { - freePictureData(); - FILE *fp = NULL; //初始化文件指针,置空 - fp = fopen(inFilePath, "rb"); //以只读&二进制方式打开文件 - if (!fp) //如果打开失败 - { - fp = NULL; - return false; - } - fseek(fp, 0, SEEK_SET); //设文件流指针到文件头部(印象中打开之后默认在尾部) - - //读取 - ID3V2Header id3v2h; //创建一个ID3v2标签头结构体 - memset(&id3v2h, 0, 10); //内存填0,10个字节 - fread(&id3v2h, 10, 1, fp); //把文件头部10个字节写入结构体内存 - - //文件头识别 - if (strncmp(id3v2h.identi, "ID3", 3) != 0) - { - fclose(fp); - fp = NULL; - return false;//没有ID3标签 - } - - //能运行到这里应该已经成功打开文件了 - - //计算整个标签长度,每个字节仅7位有效 - int tagTotalLength = (id3v2h.size[0] & 0x7f) * 0x200000 + (id3v2h.size[1] & 0x7f) * 0x4000 + (id3v2h.size[2] & 0x7f) * 0x80 + (id3v2h.size[3] & 0x7f); - - if (id3v2h.major == 3 || id3v2h.major == 4) //ID3v2.3 或 ID3v2.4 - { - ID3V2FrameHeader id3v2fh; //创建一个ID3v2标签帧头结构体 - memset(&id3v2fh, 0, 10); - - bool hasExtendedHeader = ((id3v2h.flags >> 6 & 0x1) == 1);//是否有扩展头 - - if (hasExtendedHeader) - { - //如果有扩展头 - byte extendedHeaderSize[4] = {}; - memset(&extendedHeaderSize, 0, 4); - fread(&extendedHeaderSize, 4, 1, fp); - //取得扩展头大小(不含以上数据) - int extendedHeaderLength = extendedHeaderSize[0] * 0x1000000 + extendedHeaderSize[1] * 0x10000 + extendedHeaderSize[2] * 0x100 + extendedHeaderSize[3]; - //跳过扩展头 - fseek(fp, extendedHeaderLength, SEEK_CUR); - } - - fread(&id3v2fh, 10, 1, fp); //将数据写到ID3V2FrameHeader结构体中 - int curDataLength = 10; //存放当前已经读取的数据大小,刚才已经读入10字节 - while ((strncmp(id3v2fh.FrameId, "APIC", 4) != 0))//如果帧头没有APIC标识符则循环执行 - { - if (curDataLength > tagTotalLength) - { - fclose(fp); - fp = NULL; - return false; //未发现图片数据 - } - //计算帧数据长度 - //使用int,不溢出的上限约2GB(标签帧没有这么大吧……) - int frameLength = _frameLength34(&id3v2fh, id3v2h.major); - fseek(fp, frameLength, SEEK_CUR); //向前跳跃到下一个帧头 - memset(&id3v2fh, 0, 10); //清除帧头结构体数据 - fread(&id3v2fh, 10, 1, fp); //重新读取数据 - curDataLength += frameLength + 10; //记录当前所在的ID3标签位置,以便退出循环 - } - - //计算一下当前图片帧的数据长度 - int frameLength = _frameLength34(&id3v2fh, id3v2h.major); - - /* - 这是ID3v2.3图片帧的结构: - -
- 头部10个字节的帧头 - - Text encoding $xx - 要跳过一个字节(文字编码) - - MIME type $00 - 跳过(文本 + /0),这里可得到文件格式 - - Picture type $xx - 跳过一个字节(图片类型) - - Description $00 (00) - 跳过(文本 + /0),这里可得到描述信息 - - Picture data - 这是真正的图片数据 - */ - int nonPicDataLength = 0; //非图片数据的长度 - fseek(fp, 1, SEEK_CUR); //信仰之跃 - nonPicDataLength++; - - char tempData[20] = {}; //临时存放数据的空间 - char mimeType[20] = {}; //图片类型 - int mimeTypeLength = 0; //图片类型文本长度 - - fread(&tempData, 20, 1, fp);//取得一小段数据 - fseek(fp, -20, SEEK_CUR); //回到原位 - - strcpy(mimeType, tempData); //复制出一个字符串 - mimeTypeLength = strlen(mimeType) + 1; //测试字符串长度,补上末尾00 - fseek(fp, mimeTypeLength, SEEK_CUR); //跳到此数据之后 - nonPicDataLength += mimeTypeLength; //记录长度 - - fseek(fp, 1, SEEK_CUR); //再一次信仰之跃 - nonPicDataLength++; - - int temp = 0; //记录当前字节数据的变量 - fread(&temp, 1, 1, fp); //读取一个字节 - nonPicDataLength++; //+1 - while (temp) //循环到temp为0 - { - fread(&temp, 1, 1, fp); //如果不是0继续读一字节的数据 - nonPicDataLength++; //计数 - } - //跳过了Description文本,以及末尾的\0 - - //非主流情况检测+获得文件格式 - memset(tempData, 0, 20); - fread(&tempData, 8, 1, fp); - fseek(fp, -8, SEEK_CUR); //回到原位 - //判断40次,一位一位跳到文件头 - bool ok = false; //是否正确识别出文件头 - for (int i = 0; i < 40; i++) - { - //校验文件头 - if (verificationPictureFormat(tempData)) - { - ok = true; - break; - } - else - { - //如果校验失败尝试继续向后校验 - fseek(fp, 1, SEEK_CUR); - nonPicDataLength++; - fread(&tempData, 8, 1, fp); - fseek(fp, -8, SEEK_CUR); - } - } - - if (!ok) - { - fclose(fp); - fp = NULL; - freePictureData(); - return false; //无法识别的数据 - } - //-----真正的图片数据----- - picLength = frameLength - nonPicDataLength; //计算图片数据长度 - pPicData = new byte[picLength]; //动态分配图片数据内存空间 - memset(pPicData, 0, picLength); //清空图片数据内存 - fread(pPicData, picLength, 1, fp); //得到图片数据 - //------------------------ - fclose(fp); //操作已完成,关闭文件。 - } - else if (id3v2h.major == 2) - { - //ID3v2.2 - ID3V22FrameHeader id3v2fh; //创建一个ID3v2.2标签帧头结构体 - memset(&id3v2fh, 0, 6); - fread(&id3v2fh, 6, 1, fp); //将数据写到ID3V2.2FrameHeader结构体中 - int curDataLength = 6; //存放当前已经读取的数据大小,刚才已经读入6字节 - while ((strncmp(id3v2fh.FrameId, "PIC", 3) != 0))//如果帧头没有PIC标识符则循环执行 - { - if (curDataLength > tagTotalLength) - { - fclose(fp); - fp = NULL; - return false; //未发现图片数据 - } - //计算帧数据长度 - int frameLength = id3v2fh.size[0] * 0x10000 + id3v2fh.size[1] * 0x100 + id3v2fh.size[2]; - fseek(fp, frameLength, SEEK_CUR); //向前跳跃到下一个帧头 - memset(&id3v2fh, 0, 6); //清除帧头结构体数据 - fread(&id3v2fh, 6, 1, fp); //重新读取数据 - curDataLength += frameLength + 6; //记录当前所在的ID3标签位置,以便退出循环 - } - - int frameLength = id3v2fh.size[0] * 0x10000 + id3v2fh.size[1] * 0x100 + id3v2fh.size[2]; //如果读到了图片帧,计算帧长 - - /* - 数据格式: - - Attached picture "PIC" - Frame size $xx xx xx - Text encoding $xx - Image format $xx xx xx - Picture type $xx - Description $00 (00) - Picture data - */ - - int nonPicDataLength = 0; //非图片数据的长度 - fseek(fp, 1, SEEK_CUR); //信仰之跃 Text encoding - nonPicDataLength++; - - char imageType[4] = {}; - memset(&imageType, 0, 4); - fread(&imageType, 3, 1, fp);//图像格式 - nonPicDataLength += 3; - - fseek(fp, 1, SEEK_CUR); //信仰之跃 Picture type - nonPicDataLength++; - - int temp = 0; //记录当前字节数据的变量 - fread(&temp, 1, 1, fp); //读取一个字节 - nonPicDataLength++; //+1 - while (temp) //循环到temp为0 - { - fread(&temp, 1, 1, fp); //如果不是0继续读一字节的数据 - nonPicDataLength++; //计数 - } - //跳过了Description文本,以及末尾的\0 - - //非主流情况检测 - char tempData[20] = {}; - memset(tempData, 0, 20); - fread(&tempData, 8, 1, fp); - fseek(fp, -8, SEEK_CUR); //回到原位 - //判断40次,一位一位跳到文件头 - bool ok = false; //是否正确识别出文件头 - for (int i = 0; i < 40; i++) - { - //校验文件头 - if (verificationPictureFormat(tempData)) - { - ok = true; - break; - } - else - { - //如果校验失败尝试继续向后校验 - fseek(fp, 1, SEEK_CUR); - nonPicDataLength++; - fread(&tempData, 8, 1, fp); - fseek(fp, -8, SEEK_CUR); - } - } - - if (!ok) - { - fclose(fp); - fp = NULL; - freePictureData(); - return false; //无法识别的数据 - } - //-----真正的图片数据----- - picLength = frameLength - nonPicDataLength; //计算图片数据长度 - pPicData = new byte[picLength]; //动态分配图片数据内存空间 - memset(pPicData, 0, picLength); //清空图片数据内存 - fread(pPicData, picLength, 1, fp); //得到图片数据 - //------------------------ - fclose(fp); //操作已完成,关闭文件。 - } - else - { - //其余不支持的版本 - fclose(fp);//关闭 - fp = NULL; - return false; - } - return true; - } - - //取得图片数据的长度 - int getPictureLength() - { - return picLength; - } - - //取得指向图片数据的指针 - byte *getPictureDataPtr() - { - return pPicData; - } - - //取得图片数据的扩展名(指针) - char *getPictureFormat() - { - return picFormat; - } - - bool writePictureDataToFile(const char *outFilePath) - { - FILE *fp = NULL; - if (picLength > 0) - { - fp = fopen(outFilePath, "wb"); //打开目标文件 - if (fp) //打开成功 - { - fwrite(pPicData, picLength, 1, fp); //写入文件 - fclose(fp); //关闭 - return true; - } - else - { - return false; //文件打开失败 - } - } - else - { - return false; //没有图像数据 - } - } - - //提取图片文件,参数1:输入文件,参数2:输出文件,返回值:是否成功 - bool extractPicture(const char *inFilePath, const char *outFilePath) - { - if (loadPictureData(inFilePath)) //如果取得图片数据成功 - { - if (writePictureDataToFile(outFilePath)) - { - return true; //文件写出成功 - } - else - { - return false; //文件写出失败 - } - } - else - { - return false; //无图片数据 - } - freePictureData(); - } -} -#endif \ No newline at end of file diff --git a/README.md b/README.md index 169a6a5..5a606f3 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,7 @@ I don't have a mac, so no support at all. ## Help Translation! -[Translate this project on Transifex!](https://www.transifex.com/blumia/pineapple-music/) - -Feel free to open up an issue to request an new language to translate. +TODO: move to Codeberg's Weblate. ## About License @@ -39,11 +37,3 @@ Anyway here is a list of file which is in non-free state (with license: do whate - All png images inside `icons` folder. - seekableslider.{h,cpp} - -And something from ShadowPlayer but in other license: - - - {Flac,ID3v2}Pic.h : [AlbumCoverExtractor](https://github.com/ShadowPower/AlbumCoverExtractor), with [MIT License](https://github.com/ShadowPower/AlbumCoverExtractor/blob/master/LICENSE) - -Also there are some source code which I copy-paste from Qt codebase, which released under BSD-3-Clause license by the Qt Company: - - - playlistmodel.{h,cpp} diff --git a/icons/media-repeat-single.png b/icons/media-repeat-single.png new file mode 100644 index 0000000..03aeaee Binary files /dev/null and b/icons/media-repeat-single.png differ diff --git a/languages/pineapple-music.ts b/languages/pineapple-music.ts index 647f4aa..f97bfe1 100644 --- a/languages/pineapple-music.ts +++ b/languages/pineapple-music.ts @@ -4,27 +4,27 @@ MainWindow - + Mono - + Stereo - + %1 Channels - + Select songs to play - + Audio Files @@ -55,54 +55,21 @@ - + Sample Rate: %1 Hz - + Bitrate: %1 Kbps - + Channel Count: %1 - - QMediaPlaylist - - - The file could not be accessed. - - - - - %1 playlist type is unknown - - - - - invalid line in playlist file - - - - - Invalid stream - - - - - %1 does not exist - - - - - Empty file provided - - - main diff --git a/languages/pineapple-music_zh_CN.ts b/languages/pineapple-music_zh_CN.ts index 83dbda1..25449d3 100644 --- a/languages/pineapple-music_zh_CN.ts +++ b/languages/pineapple-music_zh_CN.ts @@ -4,27 +4,27 @@ MainWindow - + Mono 单声道 - + Stereo 立体声 - + %1 Channels %1 声道 - + Select songs to play 选择要播放的曲目 - + Audio Files 音频文件 @@ -55,54 +55,21 @@ - + Sample Rate: %1 Hz 采样率: %1 Hz - + Bitrate: %1 Kbps 比特率: %1 Kbps - + Channel Count: %1 声道数: %1 - - QMediaPlaylist - - - The file could not be accessed. - - - - - %1 playlist type is unknown - - - - - invalid line in playlist file - - - - - Invalid stream - - - - - %1 does not exist - - - - - Empty file provided - - - main diff --git a/mainwindow.cpp b/mainwindow.cpp index c572ac9..87008f9 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,11 +1,7 @@ #include "mainwindow.h" #include "./ui_mainwindow.h" -#include "playlistmodel.h" -#include "qt/qmediaplaylist.h" - -#include "ID3v2Pic.h" -#include "FlacPic.h" +#include "playlistmanager.h" // taglib #ifndef NO_TAGLIB @@ -14,6 +10,7 @@ #include #include +#include #include #include #include @@ -30,11 +27,15 @@ MainWindow::MainWindow(QWidget *parent) , ui(new Ui::MainWindow) , m_mediaPlayer(new QMediaPlayer(this)) , m_audioOutput(new QAudioOutput(this)) - , m_playlistModel(new PlaylistModel(this)) + , m_playlistManager(new PlaylistManager(this)) { ui->setupUi(this); + m_playlistManager->setAutoLoadFilterSuffixes({ + "*.mp3", "*.wav", "*.aiff", "*.ape", "*.flac", "*.ogg", "*.oga", "*.mpga" + }); m_mediaPlayer->setAudioOutput(m_audioOutput); + ui->playlistView->setModel(m_playlistManager->model()); this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinimizeButtonHint); this->setAttribute(Qt::WA_TranslucentBackground, true); @@ -55,47 +56,10 @@ void MainWindow::commandlinePlayAudioFiles(QStringList audioFiles) QList audioFileUrls = strlst2urllst(audioFiles); if (!audioFileUrls.isEmpty()) { - if (audioFileUrls.count() == 1) { - loadPlaylistBySingleLocalFile(audioFileUrls.first().toLocalFile()); - } else { - createPlaylist(audioFileUrls); - } - m_mediaPlayer->play(); + QModelIndex modelIndex = m_playlistManager->loadPlaylist(audioFileUrls); } } -void MainWindow::loadPlaylistBySingleLocalFile(const QString &path) -{ - QFileInfo info(path); - QDir dir(info.path()); - QString currentFileName = info.fileName(); - QStringList entryList = dir.entryList({"*.mp3", "*.wav", "*.aiff", "*.ape", "*.flac", "*.ogg", "*.oga", "*.mpga"}, - QDir::Files | QDir::NoSymLinks, QDir::NoSort); - - QCollator collator; - collator.setNumericMode(true); - - std::sort(entryList.begin(), entryList.end(), collator); - - QList urlList; - int currentFileIndex = -1; - for (int i = 0; i < entryList.count(); i++) { - const QString & oneEntry = entryList.at(i); - urlList.append(QUrl::fromLocalFile(dir.absoluteFilePath(oneEntry))); - if (oneEntry == currentFileName) { - currentFileIndex = i; - } - } - - if (currentFileIndex == -1) { - // not in the list probably because of the suffix is not a common one, add it to the first one anyway. - urlList.prepend(QUrl::fromLocalFile(path)); - currentFileIndex = 0; - } - - createPlaylist(urlList, currentFileIndex); -} - void MainWindow::setAudioPropertyInfoForDisplay(int sampleRate, int bitrate, int channelCount, QString audioExt) { QStringList uiStrs; @@ -227,10 +191,7 @@ void MainWindow::dropEvent(QDropEvent *e) return; } - // TODO: file/format filter? - - createPlaylist(urls); - m_mediaPlayer->play(); + m_playlistManager->loadPlaylist(urls); } void MainWindow::loadFile() @@ -244,39 +205,7 @@ void MainWindow::loadFile() urlList.append(QUrl::fromLocalFile(fileName)); } - createPlaylist(urlList); -} - -/* - * The returned QMediaPlaylist* ownership belongs to the internal QMediaPlayer instance. - */ -void MainWindow::createPlaylist(QList urlList, int index) -{ - QMediaPlaylist* playlist = m_playlistModel->playlist(); - playlist->clear(); - playlist->addMedia(urlList); - - connect(playlist, &QMediaPlaylist::playbackModeChanged, this, [=](QMediaPlaylist::PlaybackMode mode) { - switch (mode) { - case QMediaPlaylist::CurrentItemInLoop: - ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-repeat-song.png")); - break; - case QMediaPlaylist::Loop: - ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-repeat.png")); - break; - case QMediaPlaylist::Sequential: - ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-normal.png")); - break; -// case QMediaPlaylist::Random: -// ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-shuffle.png")); -// break; - default: - break; - } - }); - - playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop); - playlist->setCurrentIndex(index < 0 ? 0 : index); + m_playlistManager->loadPlaylist(urlList); } void MainWindow::centerWindow() @@ -359,27 +288,12 @@ void MainWindow::on_playbackSlider_valueChanged(int value) void MainWindow::on_prevBtn_clicked() { - // QMediaPlaylist::previous() won't work when in CurrentItemInLoop playmode, - // and also works not as intended when in other playmode, so do it manually... - QMediaPlaylist * playlist = m_playlistModel->playlist(); - if (playlist) { - int index = playlist->currentIndex(); - int count = playlist->mediaCount(); - - playlist->setCurrentIndex(index == 0 ? count - 1 : index - 1); - } + m_playlistManager->setCurrentIndex(m_playlistManager->previousIndex()); } void MainWindow::on_nextBtn_clicked() { - // see also: MainWindow::on_prevBtn_clicked() - QMediaPlaylist * playlist = m_playlistModel->playlist(); - if (playlist) { - int index = playlist->currentIndex(); - int count = playlist->mediaCount(); - - playlist->setCurrentIndex(index == (count - 1) ? 0 : index + 1); - } + m_playlistManager->setCurrentIndex(m_playlistManager->nextIndex()); } void MainWindow::on_volumeBtn_clicked() @@ -404,22 +318,29 @@ void MainWindow::initUiAndAnimation() m_fadeOutAnimation->setStartValue(1); m_fadeOutAnimation->setEndValue(0); connect(m_fadeOutAnimation, &QPropertyAnimation::finished, this, &QMainWindow::close); - - // temp: a playlist for debug... - QListView * tmp_listview = new QListView(ui->pluginWidget); - tmp_listview->setModel(m_playlistModel); - tmp_listview->setGeometry({0,0,490,250}); - this->setGeometry({0,0,490,160}); // temp size, hide the playlist thing. + setFixedSize(490, 160); } void MainWindow::initConnections() { - connect(m_playlistModel->playlist(), &QMediaPlaylist::currentIndexChanged, this, [=](int currentItem) { - bool isPlaying = m_mediaPlayer->playbackState() == QMediaPlayer::PlayingState; - m_mediaPlayer->setSource(m_playlistModel->playlist()->currentMedia()); - if (isPlaying) m_mediaPlayer->play(); + connect(m_mediaPlayer, &QMediaPlayer::metaDataChanged, this, [=](){ + QMediaMetaData metadata(m_mediaPlayer->metaData()); + setAudioMetadataForDisplay(metadata.stringValue(QMediaMetaData::Title), + metadata.stringValue(QMediaMetaData::Author), + metadata.stringValue(QMediaMetaData::AlbumTitle)); + QVariant coverArt(metadata.value(QMediaMetaData::ThumbnailImage)); + if (!coverArt.isNull()) { + ui->coverLabel->setPixmap(QPixmap::fromImage(coverArt.value())); + } else { + qDebug() << "No ThumbnailImage!" << metadata.keys(); + ui->coverLabel->setPixmap(QPixmap(":/icons/icons/media-album-cover.svg")); + } }); - connect(m_playlistModel->playlist(), &QMediaPlaylist::currentMediaChanged, this, [=](const QUrl &fileUrl) { + connect(m_playlistManager, &PlaylistManager::currentIndexChanged, this, [=](int index){ + QUrl fileUrl(m_playlistManager->model()->urlByIndex(index)); + m_mediaPlayer->setSource(fileUrl); + m_mediaPlayer->play(); + ui->titleLabel->setText(fileUrl.fileName()); ui->titleLabel->setToolTip(fileUrl.fileName()); @@ -435,39 +356,7 @@ void MainWindow::initConnections() TagLib::AudioProperties *prop = fileRef.audioProperties(); setAudioPropertyInfoForDisplay(prop->sampleRate(), prop->bitrate(), prop->channels(), suffix); } - - if (!fileRef.isNull() && fileRef.tag()) { - TagLib::Tag * tag = fileRef.tag(); - setAudioMetadataForDisplay(QString::fromStdString(tag->title().to8Bit(true)), - QString::fromStdString(tag->artist().to8Bit(true)), - QString::fromStdString(tag->album().to8Bit(true))); - } #endif // NO_TAGLIB - - using namespace spID3; - using namespace spFLAC; - - bool coverLoaded = false; - - if (suffix == "MP3") { - if (spID3::loadPictureData(filePath.toLocal8Bit().data())) { - coverLoaded = true; - QByteArray picData((const char*)spID3::getPictureDataPtr(), spID3::getPictureLength()); - ui->coverLabel->setPixmap(QPixmap::fromImage(QImage::fromData(picData))); - spID3::freePictureData(); - } - } else if (suffix == "FLAC") { - if (spFLAC::loadPictureData(filePath.toLocal8Bit().data())) { - coverLoaded = true; - QByteArray picData((const char*)spFLAC::getPictureDataPtr(), spFLAC::getPictureLength()); - ui->coverLabel->setPixmap(QPixmap::fromImage(QImage::fromData(picData))); - spFLAC::freePictureData(); - } - } - - if (!coverLoaded) { - ui->coverLabel->setPixmap(QPixmap(":/icons/icons/media-album-cover.svg")); - } } }); @@ -506,35 +395,65 @@ void MainWindow::initConnections() ui->volumeSlider->setValue(vol * 100); }); -// connect(m_mediaPlayer, static_cast(&QMediaPlayer::error), -// this, [=](QMediaPlayer::Error error) { -// switch (error) { -// default: -// break; -// } -// qDebug("%s aaaaaaaaaaaaa", m_mediaPlayer->errorString().toUtf8().data()); -// }); + connect(m_mediaPlayer, &QMediaPlayer::mediaStatusChanged, this, [=](QMediaPlayer::MediaStatus status){ + if (status == QMediaPlayer::EndOfMedia) { + switch (m_playbackMode) { + case MainWindow::CurrentItemOnce: + // do nothing + break; + case MainWindow::CurrentItemInLoop: + m_mediaPlayer->play(); + break; + case MainWindow::Sequential: + on_nextBtn_clicked(); + break; + } + } + }); + + connect(this, &MainWindow::playbackModeChanged, this, [=](){ + switch (m_playbackMode) { + case MainWindow::CurrentItemOnce: + ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-repeat-single.png")); + break; + case MainWindow::CurrentItemInLoop: + ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-repeat-song.png")); + break; + case MainWindow::Sequential: + ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-repeat.png")); + break; + } + }); + + connect(m_mediaPlayer, &QMediaPlayer::errorOccurred, this, [=](QMediaPlayer::Error error, const QString &errorString) { + qDebug() << error << errorString; + }); } void MainWindow::on_playbackModeBtn_clicked() { - QMediaPlaylist * playlist = m_playlistModel->playlist(); - if (!playlist) return; - - switch (playlist->playbackMode()) { - case QMediaPlaylist::CurrentItemInLoop: - playlist->setPlaybackMode(QMediaPlaylist::Loop); + switch (m_playbackMode) { + case MainWindow::CurrentItemOnce: + setProperty("playbackMode", MainWindow::CurrentItemInLoop); break; - case QMediaPlaylist::Loop: - playlist->setPlaybackMode(QMediaPlaylist::Sequential); + case MainWindow::CurrentItemInLoop: + setProperty("playbackMode", MainWindow::Sequential); break; - case QMediaPlaylist::Sequential: - playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop); + case MainWindow::Sequential: + setProperty("playbackMode", MainWindow::CurrentItemOnce); break; -// case QMediaPlaylist::Random: -// playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop); -// break; default: break; } } + +void MainWindow::on_playListBtn_clicked() +{ + setFixedHeight(size().height() < 200 ? 420 : 160); +} + +void MainWindow::on_playlistView_activated(const QModelIndex &index) +{ + m_playlistManager->setCurrentIndex(index); +} + diff --git a/mainwindow.h b/mainwindow.h index ce83ad6..6bee16d 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -12,17 +12,25 @@ class QAudioOutput; class QPropertyAnimation; QT_END_NAMESPACE -class PlaylistModel; +class PlaylistManager; class MainWindow : public QMainWindow { Q_OBJECT public: + enum PlaybackMode { + CurrentItemOnce, + CurrentItemInLoop, + Sequential, + }; + Q_ENUM(PlaybackMode) + + Q_PROPERTY(PlaybackMode playbackMode MEMBER m_playbackMode NOTIFY playbackModeChanged) + MainWindow(QWidget *parent = nullptr); ~MainWindow() override; void commandlinePlayAudioFiles(QStringList audioFiles); - void loadPlaylistBySingleLocalFile(const QString &path); void setAudioPropertyInfoForDisplay(int sampleRate, int bitrate, int channelCount, QString audioExt); void setAudioMetadataForDisplay(QString title, QString artist, QString album); @@ -40,7 +48,6 @@ protected: void loadFile(); void centerWindow(); - void createPlaylist(QList urlList, int index = -1); private slots: void on_playbackModeBtn_clicked(); @@ -54,17 +61,25 @@ private slots: void on_volumeBtn_clicked(); void on_minimumWindowBtn_clicked(); + void on_playListBtn_clicked(); + + void on_playlistView_activated(const QModelIndex &index); + +signals: + void playbackModeChanged(enum PlaybackMode mode); + private: bool m_clickedOnWindow = false; bool m_playbackSliderPressed = false; QLinearGradient m_bgLinearGradient; + enum PlaybackMode m_playbackMode = CurrentItemInLoop; Ui::MainWindow *ui; QMediaPlayer *m_mediaPlayer; QAudioOutput *m_audioOutput; QPropertyAnimation *m_fadeOutAnimation; - PlaylistModel *m_playlistModel = nullptr; // TODO: move playback logic to player.cpp + PlaylistManager *m_playlistManager; void initUiAndAnimation(); void initConnections(); diff --git a/mainwindow.ui b/mainwindow.ui index d2e6382..c76e4f2 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -209,7 +209,7 @@ QLabel#coverLabel { - Qt::Horizontal + Qt::Orientation::Horizontal @@ -277,7 +277,7 @@ QLabel#coverLabel { - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint 10 @@ -320,7 +320,7 @@ QLabel#coverLabel { 0:00 - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -332,7 +332,7 @@ QLabel#coverLabel { 1000 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -562,7 +562,7 @@ QLabel#coverLabel { 100 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -576,9 +576,9 @@ QLabel#coverLabel { - + - + 0 0 @@ -589,6 +589,28 @@ QLabel#coverLabel { 0 + + + + 0 + + + 4 + + + 0 + + + 4 + + + 4 + + + + + + diff --git a/playlistmanager.cpp b/playlistmanager.cpp new file mode 100644 index 0000000..c9aa1e6 --- /dev/null +++ b/playlistmanager.cpp @@ -0,0 +1,255 @@ +// SPDX-FileCopyrightText: 2024 Gary Wang +// +// SPDX-License-Identifier: MIT + +#include "playlistmanager.h" + +#include +#include +#include +#include + +PlaylistModel::PlaylistModel(QObject *parent) + : QAbstractListModel(parent) +{ + +} + +PlaylistModel::~PlaylistModel() +{ + +} + +void PlaylistModel::setPlaylist(const QList &urls) +{ + beginResetModel(); + m_playlist = urls; + endResetModel(); +} + +QModelIndex PlaylistModel::loadPlaylist(const QList & urls) +{ + if (urls.isEmpty()) return QModelIndex(); + if (urls.count() == 1) { + return loadPlaylist(urls.constFirst()); + } else { + setPlaylist(urls); + return index(0); + } +} + +QModelIndex PlaylistModel::loadPlaylist(const QUrl &url) +{ + QFileInfo info(url.toLocalFile()); + QDir dir(info.path()); + QString && currentFileName = info.fileName(); + + if (dir.path() == m_currentDir) { + int idx = indexOf(url); + return idx == -1 ? appendToPlaylist(url) : index(idx); + } + + QStringList entryList = dir.entryList( + m_autoLoadSuffixes, + QDir::Files | QDir::NoSymLinks, QDir::NoSort); + + QCollator collator; + collator.setNumericMode(true); + + std::sort(entryList.begin(), entryList.end(), collator); + + QList playlist; + + int idx = -1; + for (int i = 0; i < entryList.count(); i++) { + const QString & fileName = entryList.at(i); + const QString & oneEntry = dir.absoluteFilePath(fileName); + const QUrl & url = QUrl::fromLocalFile(oneEntry); + playlist.append(url); + if (fileName == currentFileName) { + idx = i; + } + } + if (idx == -1) { + idx = playlist.count(); + playlist.append(url); + } + m_currentDir = dir.path(); + + setPlaylist(playlist); + + return index(idx); +} + +QModelIndex PlaylistModel::appendToPlaylist(const QUrl &url) +{ + const int lastIndex = rowCount(); + beginInsertRows(QModelIndex(), lastIndex, lastIndex); + m_playlist.append(url); + endInsertRows(); + return index(lastIndex); +} + +bool PlaylistModel::removeAt(int index) +{ + if (index < 0 || index >= rowCount()) return false; + beginRemoveRows(QModelIndex(), index, index); + m_playlist.removeAt(index); + endRemoveRows(); + return true; +} + +int PlaylistModel::indexOf(const QUrl &url) const +{ + return m_playlist.indexOf(url); +} + +QUrl PlaylistModel::urlByIndex(int index) const +{ + return m_playlist.value(index); +} + +QStringList PlaylistModel::autoLoadFilterSuffixes() const +{ + return m_autoLoadSuffixes; +} + +QHash PlaylistModel::roleNames() const +{ + QHash result = QAbstractListModel::roleNames(); + result.insert(UrlRole, "url"); + return result; +} + +int PlaylistModel::rowCount(const QModelIndex &parent) const +{ + return m_playlist.count(); +} + +QVariant PlaylistModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) return QVariant(); + + switch (role) { + case Qt::DisplayRole: + return m_playlist.at(index.row()).fileName(); + case UrlRole: + return m_playlist.at(index.row()); + } + + return QVariant(); +} + +PlaylistManager::PlaylistManager(QObject *parent) + : QObject(parent) +{ + connect(&m_model, &PlaylistModel::rowsRemoved, this, + [this](const QModelIndex &, int, int) { + if (m_model.rowCount() <= m_currentIndex) { + setProperty("currentIndex", m_currentIndex - 1); + } + }); + + auto onRowCountChanged = [this](){ + emit totalCountChanged(m_model.rowCount()); + }; + + connect(&m_model, &PlaylistModel::rowsInserted, this, onRowCountChanged); + connect(&m_model, &PlaylistModel::rowsRemoved, this, onRowCountChanged); + connect(&m_model, &PlaylistModel::modelReset, this, onRowCountChanged); +} + +PlaylistManager::~PlaylistManager() +{ + +} + +PlaylistModel *PlaylistManager::model() +{ + return &m_model; +} + +void PlaylistManager::setPlaylist(const QList &urls) +{ + m_model.setPlaylist(urls); +} + +QModelIndex PlaylistManager::loadPlaylist(const QList &urls) +{ + QModelIndex idx = m_model.loadPlaylist(urls); + setProperty("currentIndex", idx.row()); + return idx; +} + +QModelIndex PlaylistManager::loadPlaylist(const QUrl &url) +{ + QModelIndex idx = m_model.loadPlaylist(url); + setProperty("currentIndex", idx.row()); + return idx; +} + +int PlaylistManager::totalCount() const +{ + return m_model.rowCount(); +} + +QModelIndex PlaylistManager::previousIndex() const +{ + int count = totalCount(); + if (count == 0) return QModelIndex(); + + return m_model.index(m_currentIndex - 1 < 0 ? count - 1 : m_currentIndex - 1); +} + +QModelIndex PlaylistManager::nextIndex() const +{ + int count = totalCount(); + if (count == 0) return QModelIndex(); + + return m_model.index(m_currentIndex + 1 == count ? 0 : m_currentIndex + 1); +} + +QModelIndex PlaylistManager::curIndex() const +{ + return m_model.index(m_currentIndex); +} + +void PlaylistManager::setCurrentIndex(const QModelIndex &index) +{ + if (index.isValid() && index.row() >= 0 && index.row() < totalCount()) { + setProperty("currentIndex", index.row()); + } +} + +QUrl PlaylistManager::urlByIndex(const QModelIndex &index) +{ + return m_model.urlByIndex(index.row()); +} + +QString PlaylistManager::localFileByIndex(const QModelIndex &index) +{ + return urlByIndex(index).toLocalFile(); +} + +bool PlaylistManager::removeAt(const QModelIndex &index) +{ + return m_model.removeAt(index.row()); +} + +void PlaylistManager::setAutoLoadFilterSuffixes(const QStringList &nameFilters) +{ + m_model.setProperty("autoLoadFilterSuffixes", nameFilters); +} + +QList PlaylistManager::convertToUrlList(const QStringList &files) +{ + QList urlList; + for (const QString & str : std::as_const(files)) { + QUrl url = QUrl::fromLocalFile(str); + if (url.isValid()) { + urlList.append(url); + } + } + + return urlList; +} diff --git a/playlistmanager.h b/playlistmanager.h new file mode 100644 index 0000000..73c7549 --- /dev/null +++ b/playlistmanager.h @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2024 Gary Wang +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +class PlaylistModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum PlaylistRole { + UrlRole = Qt::UserRole + }; + Q_ENUM(PlaylistRole) + Q_PROPERTY(QStringList autoLoadFilterSuffixes MEMBER m_autoLoadSuffixes NOTIFY autoLoadFilterSuffixesChanged) + + explicit PlaylistModel(QObject *parent = nullptr); + ~PlaylistModel(); + + void setPlaylist(const QList & urls); + QModelIndex loadPlaylist(const QList & urls); + QModelIndex loadPlaylist(const QUrl & url); + QModelIndex appendToPlaylist(const QUrl & url); + bool removeAt(int index); + int indexOf(const QUrl & url) const; + QUrl urlByIndex(int index) const; + QStringList autoLoadFilterSuffixes() const; + + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +signals: + void autoLoadFilterSuffixesChanged(QStringList suffixes); + +private: + // model data + QList m_playlist; + // properties + QStringList m_autoLoadSuffixes = {}; + // internal + QString m_currentDir; +}; + +class PlaylistManager : public QObject +{ + Q_OBJECT +public: + Q_PROPERTY(int currentIndex MEMBER m_currentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(QStringList autoLoadFilterSuffixes WRITE setAutoLoadFilterSuffixes) + Q_PROPERTY(PlaylistModel * model READ model CONSTANT) + + explicit PlaylistManager(QObject *parent = nullptr); + ~PlaylistManager(); + + PlaylistModel * model(); + + void setPlaylist(const QList & url); + Q_INVOKABLE QModelIndex loadPlaylist(const QList & urls); + Q_INVOKABLE QModelIndex loadPlaylist(const QUrl & url); + + int totalCount() const; + QModelIndex previousIndex() const; + QModelIndex nextIndex() const; + QModelIndex curIndex() const; + void setCurrentIndex(const QModelIndex & index); + QUrl urlByIndex(const QModelIndex & index); + QString localFileByIndex(const QModelIndex & index); + bool removeAt(const QModelIndex & index); + + void setAutoLoadFilterSuffixes(const QStringList &nameFilters); + + static QList convertToUrlList(const QStringList & files); + +signals: + void currentIndexChanged(int index); + void totalCountChanged(int count); + +private: + int m_currentIndex = -1; + PlaylistModel m_model; +}; diff --git a/playlistmodel.cpp b/playlistmodel.cpp deleted file mode 100644 index d05b10c..0000000 --- a/playlistmodel.cpp +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "playlistmodel.h" -#include "qt/qmediaplaylist.h" - -#include -#include - -PlaylistModel::PlaylistModel(QObject *parent) - : QAbstractItemModel(parent) -{ - m_playlist.reset(new QMediaPlaylist); - connect(m_playlist.data(), &QMediaPlaylist::mediaAboutToBeInserted, this, &PlaylistModel::beginInsertItems); - connect(m_playlist.data(), &QMediaPlaylist::mediaInserted, this, &PlaylistModel::endInsertItems); - connect(m_playlist.data(), &QMediaPlaylist::mediaAboutToBeRemoved, this, &PlaylistModel::beginRemoveItems); - connect(m_playlist.data(), &QMediaPlaylist::mediaRemoved, this, &PlaylistModel::endRemoveItems); - connect(m_playlist.data(), &QMediaPlaylist::mediaChanged, this, &PlaylistModel::changeItems); -} - -PlaylistModel::~PlaylistModel() = default; - -int PlaylistModel::rowCount(const QModelIndex &parent) const -{ - return m_playlist && !parent.isValid() ? m_playlist->mediaCount() : 0; -} - -int PlaylistModel::columnCount(const QModelIndex &parent) const -{ - return !parent.isValid() ? ColumnCount : 0; -} - -QModelIndex PlaylistModel::index(int row, int column, const QModelIndex &parent) const -{ - return m_playlist && !parent.isValid() - && row >= 0 && row < m_playlist->mediaCount() - && column >= 0 && column < ColumnCount - ? createIndex(row, column) - : QModelIndex(); -} - -QModelIndex PlaylistModel::parent(const QModelIndex &child) const -{ - Q_UNUSED(child); - - return QModelIndex(); -} - -QVariant PlaylistModel::data(const QModelIndex &index, int role) const -{ - if (index.isValid() && role == Qt::DisplayRole) { - QVariant value = m_data[index]; - if (!value.isValid() && index.column() == Title) { - QUrl location = m_playlist->media(index.row()); - return QFileInfo(location.path()).fileName(); - } - - return value; - } - return QVariant(); -} - -QMediaPlaylist *PlaylistModel::playlist() const -{ - return m_playlist.data(); -} - -bool PlaylistModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - Q_UNUSED(role); - m_data[index] = value; - emit dataChanged(index, index); - return true; -} - -void PlaylistModel::beginInsertItems(int start, int end) -{ - m_data.clear(); - beginInsertRows(QModelIndex(), start, end); -} - -void PlaylistModel::endInsertItems() -{ - endInsertRows(); -} - -void PlaylistModel::beginRemoveItems(int start, int end) -{ - m_data.clear(); - beginRemoveRows(QModelIndex(), start, end); -} - -void PlaylistModel::endRemoveItems() -{ - endInsertRows(); -} - -void PlaylistModel::changeItems(int start, int end) -{ - m_data.clear(); - emit dataChanged(index(start,0), index(end,ColumnCount)); -} diff --git a/playlistmodel.h b/playlistmodel.h deleted file mode 100644 index 6c20cc1..0000000 --- a/playlistmodel.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef PLAYLISTMODEL_H -#define PLAYLISTMODEL_H - -#include -#include - -QT_BEGIN_NAMESPACE -class QMediaPlaylist; -QT_END_NAMESPACE - -class PlaylistModel : public QAbstractItemModel -{ - Q_OBJECT - -public: - enum Column - { - Title = 0, - ColumnCount - }; - - explicit PlaylistModel(QObject *parent = nullptr); - ~PlaylistModel(); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &child) const override; - - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - QMediaPlaylist *playlist() const; - - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override; - -private slots: - void beginInsertItems(int start, int end); - void endInsertItems(); - void beginRemoveItems(int start, int end); - void endRemoveItems(); - void changeItems(int start, int end); - -private: - QScopedPointer m_playlist; - QMap m_data; -}; - -#endif // PLAYLISTMODEL_H diff --git a/qt/qmediaplaylist.cpp b/qt/qmediaplaylist.cpp deleted file mode 100644 index 5297208..0000000 --- a/qt/qmediaplaylist.cpp +++ /dev/null @@ -1,653 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "qmediaplaylist.h" -#include "qmediaplaylist_p.h" -#include "qplaylistfileparser_p.h" - -#include -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class QM3uPlaylistWriter -{ -public: - QM3uPlaylistWriter(QIODevice *device) - :m_device(device), m_textStream(new QTextStream(m_device)) - { - } - - ~QM3uPlaylistWriter() - { - delete m_textStream; - } - - bool writeItem(const QUrl& item) - { - *m_textStream << item.toString() << Qt::endl; - return true; - } - -private: - QIODevice *m_device; - QTextStream *m_textStream; -}; - - -int QMediaPlaylistPrivate::nextPosition(int steps) const -{ - if (playlist.count() == 0) - return -1; - - int next = currentPos + steps; - - switch (playbackMode) { - case QMediaPlaylist::CurrentItemOnce: - return steps != 0 ? -1 : currentPos; - case QMediaPlaylist::CurrentItemInLoop: - return currentPos; - case QMediaPlaylist::Sequential: - if (next >= playlist.size()) - next = -1; - break; - case QMediaPlaylist::Loop: - next %= playlist.count(); - break; - } - - return next; -} - -int QMediaPlaylistPrivate::prevPosition(int steps) const -{ - if (playlist.count() == 0) - return -1; - - int next = currentPos; - if (next < 0) - next = playlist.size(); - next -= steps; - - switch (playbackMode) { - case QMediaPlaylist::CurrentItemOnce: - return steps != 0 ? -1 : currentPos; - case QMediaPlaylist::CurrentItemInLoop: - return currentPos; - case QMediaPlaylist::Sequential: - if (next < 0) - next = -1; - break; - case QMediaPlaylist::Loop: - next %= playlist.size(); - if (next < 0) - next += playlist.size(); - break; - } - - return next; -} - -/*! - \class QMediaPlaylist - \inmodule QtMultimedia - \ingroup multimedia - \ingroup multimedia_playback - - - \brief The QMediaPlaylist class provides a list of media content to play. - - QMediaPlaylist is intended to be used with other media objects, - like QMediaPlayer. - - QMediaPlaylist allows to access the service intrinsic playlist functionality - if available, otherwise it provides the local memory playlist implementation. - - \snippet multimedia-snippets/media.cpp Movie playlist - - Depending on playlist source implementation, most of the playlist mutating - operations can be asynchronous. - - QMediaPlayList currently supports M3U playlists (file extension .m3u and .m3u8). - - \sa QUrl -*/ - - -/*! - \enum QMediaPlaylist::PlaybackMode - - The QMediaPlaylist::PlaybackMode describes the order items in playlist are played. - - \value CurrentItemOnce The current item is played only once. - - \value CurrentItemInLoop The current item is played repeatedly in a loop. - - \value Sequential Playback starts from the current and moves through each successive item until the last is reached and then stops. - The next item is a null item when the last one is currently playing. - - \value Loop Playback restarts at the first item after the last has finished playing. - - \value Random Play items in random order. -*/ - - - -/*! - Create a new playlist object with the given \a parent. -*/ - -QMediaPlaylist::QMediaPlaylist(QObject *parent) - : QObject(parent) - , d_ptr(new QMediaPlaylistPrivate) -{ - Q_D(QMediaPlaylist); - - d->q_ptr = this; -} - -/*! - Destroys the playlist. - */ - -QMediaPlaylist::~QMediaPlaylist() -{ - delete d_ptr; -} - -/*! - \property QMediaPlaylist::playbackMode - - This property defines the order that items in the playlist are played. - - \sa QMediaPlaylist::PlaybackMode -*/ - -QMediaPlaylist::PlaybackMode QMediaPlaylist::playbackMode() const -{ - return d_func()->playbackMode; -} - -void QMediaPlaylist::setPlaybackMode(QMediaPlaylist::PlaybackMode mode) -{ - Q_D(QMediaPlaylist); - - if (mode == d->playbackMode) - return; - - d->playbackMode = mode; - - emit playbackModeChanged(mode); -} - -/*! - Returns position of the current media content in the playlist. -*/ -int QMediaPlaylist::currentIndex() const -{ - return d_func()->currentPos; -} - -/*! - Returns the current media content. -*/ - -QUrl QMediaPlaylist::currentMedia() const -{ - Q_D(const QMediaPlaylist); - if (d->currentPos < 0 || d->currentPos >= d->playlist.size()) - return QUrl(); - return d_func()->playlist.at(d_func()->currentPos); -} - -/*! - Returns the index of the item, which would be current after calling next() - \a steps times. - - Returned value depends on the size of playlist, current position - and playback mode. - - \sa QMediaPlaylist::playbackMode(), previousIndex() -*/ -int QMediaPlaylist::nextIndex(int steps) const -{ - return d_func()->nextPosition(steps); -} - -/*! - Returns the index of the item, which would be current after calling previous() - \a steps times. - - \sa QMediaPlaylist::playbackMode(), nextIndex() -*/ - -int QMediaPlaylist::previousIndex(int steps) const -{ - return d_func()->prevPosition(steps); -} - - -/*! - Returns the number of items in the playlist. - - \sa isEmpty() - */ -int QMediaPlaylist::mediaCount() const -{ - return d_func()->playlist.count(); -} - -/*! - Returns true if the playlist contains no items, otherwise returns false. - - \sa mediaCount() - */ -bool QMediaPlaylist::isEmpty() const -{ - return mediaCount() == 0; -} - -/*! - Returns the media content at \a index in the playlist. -*/ - -QUrl QMediaPlaylist::media(int index) const -{ - Q_D(const QMediaPlaylist); - if (index < 0 || index >= d->playlist.size()) - return QUrl(); - return d->playlist.at(index); -} - -/*! - Append the media \a content to the playlist. - - Returns true if the operation is successful, otherwise returns false. - */ -void QMediaPlaylist::addMedia(const QUrl &content) -{ - Q_D(QMediaPlaylist); - int pos = d->playlist.size(); - emit mediaAboutToBeInserted(pos, pos); - d->playlist.append(content); - emit mediaInserted(pos, pos); -} - -/*! - Append multiple media content \a items to the playlist. - - Returns true if the operation is successful, otherwise returns false. - */ -void QMediaPlaylist::addMedia(const QList &items) -{ - if (!items.size()) - return; - - Q_D(QMediaPlaylist); - int first = d->playlist.size(); - int last = first + items.size() - 1; - emit mediaAboutToBeInserted(first, last); - d_func()->playlist.append(items); - emit mediaInserted(first, last); -} - -/*! - Insert the media \a content to the playlist at position \a pos. - - Returns true if the operation is successful, otherwise returns false. -*/ - -bool QMediaPlaylist::insertMedia(int pos, const QUrl &content) -{ - Q_D(QMediaPlaylist); - pos = qBound(0, pos, d->playlist.size()); - emit mediaAboutToBeInserted(pos, pos); - d->playlist.insert(pos, content); - emit mediaInserted(pos, pos); - return true; -} - -/*! - Insert multiple media content \a items to the playlist at position \a pos. - - Returns true if the operation is successful, otherwise returns false. -*/ - -bool QMediaPlaylist::insertMedia(int pos, const QList &items) -{ - if (!items.size()) - return true; - - Q_D(QMediaPlaylist); - pos = qBound(0, pos, d->playlist.size()); - int last = pos + items.size() - 1; - emit mediaAboutToBeInserted(pos, last); - auto newList = d->playlist.mid(0, pos); - newList += items; - newList += d->playlist.mid(pos); - d->playlist = newList; - emit mediaInserted(pos, last); - return true; -} - -/*! - Move the item from position \a from to position \a to. - - Returns true if the operation is successful, otherwise false. - - \since 5.7 -*/ -bool QMediaPlaylist::moveMedia(int from, int to) -{ - Q_D(QMediaPlaylist); - if (from < 0 || from > d->playlist.count() || - to < 0 || to > d->playlist.count()) - return false; - - d->playlist.move(from, to); - emit mediaChanged(from, to); - return true; -} - -/*! - Remove the item from the playlist at position \a pos. - - Returns true if the operation is successful, otherwise return false. - */ -bool QMediaPlaylist::removeMedia(int pos) -{ - return removeMedia(pos, pos); -} - -/*! - Remove items in the playlist from \a start to \a end inclusive. - - Returns true if the operation is successful, otherwise return false. - */ -bool QMediaPlaylist::removeMedia(int start, int end) -{ - Q_D(QMediaPlaylist); - if (end < start || end < 0 || start >= d->playlist.count()) - return false; - start = qBound(0, start, d->playlist.size() - 1); - end = qBound(0, end, d->playlist.size() - 1); - - emit mediaAboutToBeRemoved(start, end); - d->playlist.remove(start, end - start + 1); - emit mediaRemoved(start, end); - return true; -} - -/*! - Remove all the items from the playlist. - - Returns true if the operation is successful, otherwise return false. - */ -void QMediaPlaylist::clear() -{ - Q_D(QMediaPlaylist); - int size = d->playlist.size(); - emit mediaAboutToBeRemoved(0, size - 1); - d->playlist.clear(); - emit mediaRemoved(0, size - 1); -} - -/*! - Load playlist from \a location. If \a format is specified, it is used, - otherwise format is guessed from location name and data. - - New items are appended to playlist. - - QMediaPlaylist::loaded() signal is emitted if playlist was loaded successfully, - otherwise the playlist emits loadFailed(). -*/ - -void QMediaPlaylist::load(const QUrl &location, const char *format) -{ - Q_D(QMediaPlaylist); - - d->error = NoError; - d->errorString.clear(); - - d->ensureParser(); - d->parser->start(location, QString::fromUtf8(format)); -} - -/*! - Load playlist from QIODevice \a device. If \a format is specified, it is used, - otherwise format is guessed from device data. - - New items are appended to playlist. - - QMediaPlaylist::loaded() signal is emitted if playlist was loaded successfully, - otherwise the playlist emits loadFailed(). -*/ -void QMediaPlaylist::load(QIODevice *device, const char *format) -{ - Q_D(QMediaPlaylist); - - d->error = NoError; - d->errorString.clear(); - - d->ensureParser(); - d->parser->start(device, QString::fromUtf8(format)); -} - -/*! - Save playlist to \a location. If \a format is specified, it is used, - otherwise format is guessed from location name. - - Returns true if playlist was saved successfully, otherwise returns false. - */ -bool QMediaPlaylist::save(const QUrl &location, const char *format) const -{ - Q_D(const QMediaPlaylist); - - d->error = NoError; - d->errorString.clear(); - - if (!d->checkFormat(format)) - return false; - - QFile file(location.toLocalFile()); - - if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - d->error = AccessDeniedError; - d->errorString = tr("The file could not be accessed."); - return false; - } - - return save(&file, format); -} - -/*! - Save playlist to QIODevice \a device using format \a format. - - Returns true if playlist was saved successfully, otherwise returns false. -*/ -bool QMediaPlaylist::save(QIODevice *device, const char *format) const -{ - Q_D(const QMediaPlaylist); - - d->error = NoError; - d->errorString.clear(); - - if (!d->checkFormat(format)) - return false; - - QM3uPlaylistWriter writer(device); - for (const auto &entry : d->playlist) - writer.writeItem(entry); - return true; -} - -/*! - Returns the last error condition. -*/ -QMediaPlaylist::Error QMediaPlaylist::error() const -{ - return d_func()->error; -} - -/*! - Returns the string describing the last error condition. -*/ -QString QMediaPlaylist::errorString() const -{ - return d_func()->errorString; -} - -/*! - Shuffle items in the playlist. -*/ -void QMediaPlaylist::shuffle() -{ - Q_D(QMediaPlaylist); - QList playlist; - - // keep the current item when shuffling - QUrl current; - if (d->currentPos != -1) - current = d->playlist.takeAt(d->currentPos); - - while (!d->playlist.isEmpty()) - playlist.append(d->playlist.takeAt(QRandomGenerator::global()->bounded(int(d->playlist.size())))); - - if (d->currentPos != -1) - playlist.insert(d->currentPos, current); - d->playlist = playlist; - emit mediaChanged(0, d->playlist.count()); -} - - -/*! - Advance to the next media content in playlist. -*/ -void QMediaPlaylist::next() -{ - Q_D(QMediaPlaylist); - d->currentPos = d->nextPosition(1); - - emit currentIndexChanged(d->currentPos); - emit currentMediaChanged(currentMedia()); -} - -/*! - Return to the previous media content in playlist. -*/ -void QMediaPlaylist::previous() -{ - Q_D(QMediaPlaylist); - d->currentPos = d->prevPosition(1); - - emit currentIndexChanged(d->currentPos); - emit currentMediaChanged(currentMedia()); -} - -/*! - Activate media content from playlist at position \a playlistPosition. -*/ - -void QMediaPlaylist::setCurrentIndex(int playlistPosition) -{ - Q_D(QMediaPlaylist); - if (playlistPosition < 0 || playlistPosition >= d->playlist.size()) - playlistPosition = -1; - d->currentPos = playlistPosition; - - emit currentIndexChanged(d->currentPos); - emit currentMediaChanged(currentMedia()); -} - -/*! - \fn void QMediaPlaylist::mediaInserted(int start, int end) - - This signal is emitted after media has been inserted into the playlist. - The new items are those between \a start and \a end inclusive. - */ - -/*! - \fn void QMediaPlaylist::mediaRemoved(int start, int end) - - This signal is emitted after media has been removed from the playlist. - The removed items are those between \a start and \a end inclusive. - */ - -/*! - \fn void QMediaPlaylist::mediaChanged(int start, int end) - - This signal is emitted after media has been changed in the playlist - between \a start and \a end positions inclusive. - */ - -/*! - \fn void QMediaPlaylist::currentIndexChanged(int position) - - Signal emitted when playlist position changed to \a position. -*/ - -/*! - \fn void QMediaPlaylist::playbackModeChanged(QMediaPlaylist::PlaybackMode mode) - - Signal emitted when playback mode changed to \a mode. -*/ - -/*! - \fn void QMediaPlaylist::mediaAboutToBeInserted(int start, int end) - - Signal emitted when items are to be inserted at \a start and ending at \a end. -*/ - -/*! - \fn void QMediaPlaylist::mediaAboutToBeRemoved(int start, int end) - - Signal emitted when item are to be deleted at \a start and ending at \a end. -*/ - -/*! - \fn void QMediaPlaylist::currentMediaChanged(const QUrl &content) - - Signal emitted when current media changes to \a content. -*/ - -/*! - \property QMediaPlaylist::currentIndex - \brief Current position. -*/ - -/*! - \property QMediaPlaylist::currentMedia - \brief Current media content. -*/ - -/*! - \fn QMediaPlaylist::loaded() - - Signal emitted when playlist finished loading. -*/ - -/*! - \fn QMediaPlaylist::loadFailed() - - Signal emitted if failed to load playlist. -*/ - -/*! - \enum QMediaPlaylist::Error - - This enum describes the QMediaPlaylist error codes. - - \value NoError No errors. - \value FormatError Format error. - \value FormatNotSupportedError Format not supported. - \value NetworkError Network error. - \value AccessDeniedError Access denied error. -*/ - -QT_END_NAMESPACE - -#include "moc_qmediaplaylist.cpp" diff --git a/qt/qmediaplaylist.h b/qt/qmediaplaylist.h deleted file mode 100644 index 94846d9..0000000 --- a/qt/qmediaplaylist.h +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef QMEDIAPLAYLIST_H -#define QMEDIAPLAYLIST_H - -#include - -#include -#include - - -QT_BEGIN_NAMESPACE - -class QMediaPlaylistPrivate; -class QMediaPlaylist : public QObject -{ - Q_OBJECT - Q_PROPERTY(QMediaPlaylist::PlaybackMode playbackMode READ playbackMode WRITE setPlaybackMode NOTIFY playbackModeChanged) - Q_PROPERTY(QUrl currentMedia READ currentMedia NOTIFY currentMediaChanged) - Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) - -public: - enum PlaybackMode { CurrentItemOnce, CurrentItemInLoop, Sequential, Loop }; - Q_ENUM(PlaybackMode) - enum Error { NoError, FormatError, FormatNotSupportedError, NetworkError, AccessDeniedError }; - Q_ENUM(Error) - - explicit QMediaPlaylist(QObject *parent = nullptr); - virtual ~QMediaPlaylist(); - - PlaybackMode playbackMode() const; - void setPlaybackMode(PlaybackMode mode); - - int currentIndex() const; - QUrl currentMedia() const; - - int nextIndex(int steps = 1) const; - int previousIndex(int steps = 1) const; - - QUrl media(int index) const; - - int mediaCount() const; - bool isEmpty() const; - - void addMedia(const QUrl &content); - void addMedia(const QList &items); - bool insertMedia(int index, const QUrl &content); - bool insertMedia(int index, const QList &items); - bool moveMedia(int from, int to); - bool removeMedia(int pos); - bool removeMedia(int start, int end); - void clear(); - - void load(const QUrl &location, const char *format = nullptr); - void load(QIODevice *device, const char *format = nullptr); - - bool save(const QUrl &location, const char *format = nullptr) const; - bool save(QIODevice *device, const char *format) const; - - Error error() const; - QString errorString() const; - -public Q_SLOTS: - void shuffle(); - - void next(); - void previous(); - - void setCurrentIndex(int index); - -Q_SIGNALS: - void currentIndexChanged(int index); - void playbackModeChanged(QMediaPlaylist::PlaybackMode mode); - void currentMediaChanged(const QUrl&); - - void mediaAboutToBeInserted(int start, int end); - void mediaInserted(int start, int end); - void mediaAboutToBeRemoved(int start, int end); - void mediaRemoved(int start, int end); - void mediaChanged(int start, int end); - - void loaded(); - void loadFailed(); - -private: - QMediaPlaylistPrivate *d_ptr; - Q_DECLARE_PRIVATE(QMediaPlaylist) -}; - -QT_END_NAMESPACE - -Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, PlaybackMode) -Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, Error) - -#endif // QMEDIAPLAYLIST_H diff --git a/qt/qmediaplaylist_p.h b/qt/qmediaplaylist_p.h deleted file mode 100644 index b0a6609..0000000 --- a/qt/qmediaplaylist_p.h +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef QMEDIAPLAYLIST_P_H -#define QMEDIAPLAYLIST_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "qmediaplaylist.h" -#include "qplaylistfileparser_p.h" - -#include - -#ifdef Q_MOC_RUN -# pragma Q_MOC_EXPAND_MACROS -#endif - -QT_BEGIN_NAMESPACE - - -class QMediaPlaylistControl; - -class QMediaPlaylistPrivate -{ - Q_DECLARE_PUBLIC(QMediaPlaylist) -public: - QMediaPlaylistPrivate() - : error(QMediaPlaylist::NoError) - { - } - - virtual ~QMediaPlaylistPrivate() - { - if (parser) - delete parser; - } - - void loadFailed(QMediaPlaylist::Error error, const QString &errorString) - { - this->error = error; - this->errorString = errorString; - - emit q_ptr->loadFailed(); - } - - void loadFinished() - { - q_ptr->addMedia(parser->playlist); - - emit q_ptr->loaded(); - } - - bool checkFormat(const char *format) const - { - QLatin1String f(format); - QPlaylistFileParser::FileType type = format ? QPlaylistFileParser::UNKNOWN : QPlaylistFileParser::M3U8; - if (format) { - if (f == QLatin1String("m3u") || f == QLatin1String("text/uri-list") || - f == QLatin1String("audio/x-mpegurl") || f == QLatin1String("audio/mpegurl")) - type = QPlaylistFileParser::M3U; - else if (f == QLatin1String("m3u8") || f == QLatin1String("application/x-mpegURL") || - f == QLatin1String("application/vnd.apple.mpegurl")) - type = QPlaylistFileParser::M3U8; - } - - if (type == QPlaylistFileParser::UNKNOWN || type == QPlaylistFileParser::PLS) { - error = QMediaPlaylist::FormatNotSupportedError; - errorString = QMediaPlaylist::tr("This file format is not supported."); - return false; - } - return true; - } - - void ensureParser() - { - if (parser) - return; - - parser = new QPlaylistFileParser(q_ptr); - QObject::connect(parser, &QPlaylistFileParser::finished, [this]() { loadFinished(); }); - QObject::connect(parser, &QPlaylistFileParser::error, - [this](QMediaPlaylist::Error err, const QString& errorMsg) { loadFailed(err, errorMsg); }); - } - - int nextPosition(int steps) const; - int prevPosition(int steps) const; - - QList playlist; - - int currentPos = -1; - QMediaPlaylist::PlaybackMode playbackMode = QMediaPlaylist::Sequential; - - QPlaylistFileParser *parser = nullptr; - mutable QMediaPlaylist::Error error; - mutable QString errorString; - - QMediaPlaylist *q_ptr; -}; - -QT_END_NAMESPACE - - -#endif // QMEDIAPLAYLIST_P_H diff --git a/qt/qplaylistfileparser.cpp b/qt/qplaylistfileparser.cpp deleted file mode 100644 index 698f81d..0000000 --- a/qt/qplaylistfileparser.cpp +++ /dev/null @@ -1,605 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "qplaylistfileparser_p.h" -#include -#include -#include -#include -#include -#include -#include "qmediaplayer.h" -#include "qmediametadata.h" - -QT_BEGIN_NAMESPACE - -namespace { - -class ParserBase -{ -public: - explicit ParserBase(QPlaylistFileParser *parent) - : m_parent(parent) - , m_aborted(false) - { - Q_ASSERT(m_parent); - } - - bool parseLine(int lineIndex, const QString& line, const QUrl& root) - { - if (m_aborted) - return false; - - const bool ok = parseLineImpl(lineIndex, line, root); - return ok && !m_aborted; - } - - virtual void abort() { m_aborted = true; } - virtual ~ParserBase() = default; - -protected: - virtual bool parseLineImpl(int lineIndex, const QString& line, const QUrl& root) = 0; - - static QUrl expandToFullPath(const QUrl &root, const QString &line) - { - // On Linux, backslashes are not converted to forward slashes :/ - if (line.startsWith(QLatin1String("//")) || line.startsWith(QLatin1String("\\\\"))) { - // Network share paths are not resolved - return QUrl::fromLocalFile(line); - } - - QUrl url(line); - if (url.scheme().isEmpty()) { - // Resolve it relative to root - if (root.isLocalFile()) - return QUrl::fromUserInput(line, root.adjusted(QUrl::RemoveFilename).toLocalFile(), QUrl::AssumeLocalFile); - return root.resolved(url); - } - if (url.scheme().length() == 1) - // Assume it's a drive letter for a Windows path - url = QUrl::fromLocalFile(line); - - return url; - } - - void newItemFound(const QVariant& content) { Q_EMIT m_parent->newItem(content); } - - - QPlaylistFileParser *m_parent; - bool m_aborted; -}; - -class M3UParser : public ParserBase -{ -public: - explicit M3UParser(QPlaylistFileParser *q) - : ParserBase(q) - , m_extendedFormat(false) - { - } - - /* - * - Extended M3U directives - - #EXTM3U - header - must be first line of file - #EXTINF - extra info - length (seconds), title - #EXTINF - extra info - length (seconds), artist '-' title - - Example - - #EXTM3U - #EXTINF:123, Sample artist - Sample title - C:\Documents and Settings\I\My Music\Sample.mp3 - #EXTINF:321,Example Artist - Example title - C:\Documents and Settings\I\My Music\Greatest Hits\Example.ogg - - */ - bool parseLineImpl(int lineIndex, const QString& line, const QUrl& root) override - { - if (line[0] == u'#' ) { - if (m_extendedFormat) { - if (line.startsWith(QLatin1String("#EXTINF:"))) { - m_extraInfo.clear(); - int artistStart = line.indexOf(QLatin1String(","), 8); - bool ok = false; - QStringView lineView { line }; - int length = lineView.mid(8, artistStart < 8 ? -1 : artistStart - 8).trimmed().toInt(&ok); - if (ok && length > 0) { - //convert from second to milisecond - m_extraInfo[QMediaMetaData::Duration] = QVariant(length * 1000); - } - if (artistStart > 0) { - int titleStart = getSplitIndex(line, artistStart); - if (titleStart > artistStart) { - m_extraInfo[QMediaMetaData::Author] = lineView.mid(artistStart + 1, - titleStart - artistStart - 1).trimmed().toString(). - replace(QLatin1String("--"), QLatin1String("-")); - m_extraInfo[QMediaMetaData::Title] = lineView.mid(titleStart + 1).trimmed().toString(). - replace(QLatin1String("--"), QLatin1String("-")); - } else { - m_extraInfo[QMediaMetaData::Title] = lineView.mid(artistStart + 1).trimmed().toString(). - replace(QLatin1String("--"), QLatin1String("-")); - } - } - } - } else if (lineIndex == 0 && line.startsWith(QLatin1String("#EXTM3U"))) { - m_extendedFormat = true; - } - } else { - QUrl url = expandToFullPath(root, line); - m_extraInfo[QMediaMetaData::Url] = url; - m_parent->playlist.append(url); - newItemFound(QVariant::fromValue(m_extraInfo)); - m_extraInfo.clear(); - } - - return true; - } - - int getSplitIndex(const QString& line, int startPos) - { - if (startPos < 0) - startPos = 0; - const QChar* buf = line.data(); - for (int i = startPos; i < line.length(); ++i) { - if (buf[i] == u'-') { - if (i == line.length() - 1) - return i; - ++i; - if (buf[i] != u'-') - return i - 1; - } - } - return -1; - } - -private: - QMediaMetaData m_extraInfo; - bool m_extendedFormat; -}; - -class PLSParser : public ParserBase -{ -public: - explicit PLSParser(QPlaylistFileParser *q) - : ParserBase(q) - { - } - -/* - * -The format is essentially that of an INI file structured as follows: - -Header - - * [playlist] : This tag indicates that it is a Playlist File - -Track Entry -Assuming track entry #X - - * FileX : Variable defining location of stream. - * TitleX : Defines track title. - * LengthX : Length in seconds of track. Value of -1 indicates indefinite. - -Footer - - * NumberOfEntries : This variable indicates the number of tracks. - * Version : Playlist version. Currently only a value of 2 is valid. - -[playlist] - -File1=Alternative\everclear - SMFTA.mp3 - -Title1=Everclear - So Much For The Afterglow - -Length1=233 - -File2=http://www.site.com:8000/listen.pls - -Title2=My Cool Stream - -Length5=-1 - -NumberOfEntries=2 - -Version=2 -*/ - bool parseLineImpl(int, const QString &line, const QUrl &root) override - { - // We ignore everything but 'File' entries, since that's the only thing we care about. - if (!line.startsWith(QLatin1String("File"))) - return true; - - QString value = getValue(line); - if (value.isEmpty()) - return true; - - QUrl path = expandToFullPath(root, value); - m_parent->playlist.append(path); - newItemFound(path); - - return true; - } - - QString getValue(QStringView line) { - int start = line.indexOf(u'='); - if (start < 0) - return QString(); - return line.mid(start + 1).trimmed().toString(); - } -}; -} - -///////////////////////////////////////////////////////////////////////////////////////////////// - -class QPlaylistFileParserPrivate -{ - Q_DECLARE_PUBLIC(QPlaylistFileParser) -public: - QPlaylistFileParserPrivate(QPlaylistFileParser *q) - : q_ptr(q) - , m_stream(nullptr) - , m_type(QPlaylistFileParser::UNKNOWN) - , m_scanIndex(0) - , m_lineIndex(-1) - , m_utf8(false) - , m_aborted(false) - { - } - - void handleData(); - void handleParserFinished(); - void abort(); - void reset(); - - QScopedPointer m_source; - QScopedPointer m_currentParser; - QByteArray m_buffer; - QUrl m_root; - QNetworkAccessManager m_mgr; - QString m_mimeType; - QPlaylistFileParser *q_ptr; - QPointer m_stream; - QPlaylistFileParser::FileType m_type; - struct ParserJob - { - QIODevice *m_stream; - QUrl m_media; - QString m_mimeType; - [[nodiscard]] bool isValid() const { return m_stream || !m_media.isEmpty(); } - void reset() { m_stream = nullptr; m_media = QUrl(); m_mimeType = QString(); } - } m_pendingJob; - int m_scanIndex; - int m_lineIndex; - bool m_utf8; - bool m_aborted; - -private: - bool processLine(int startIndex, int length); -}; - -#define LINE_LIMIT 4096 -#define READ_LIMIT 64 - -bool QPlaylistFileParserPrivate::processLine(int startIndex, int length) -{ - Q_Q(QPlaylistFileParser); - m_lineIndex++; - - if (!m_currentParser) { - const QString urlString = m_root.toString(); - const QString &suffix = !urlString.isEmpty() ? QFileInfo(urlString).suffix() : urlString; - QString mimeType; - if (m_source) - mimeType = m_source->header(QNetworkRequest::ContentTypeHeader).toString(); - m_type = QPlaylistFileParser::findPlaylistType(suffix, !mimeType.isEmpty() ? mimeType : m_mimeType, m_buffer.constData(), quint32(m_buffer.size())); - - switch (m_type) { - case QPlaylistFileParser::UNKNOWN: - emit q->error(QMediaPlaylist::FormatError, - QMediaPlaylist::tr("%1 playlist type is unknown").arg(m_root.toString())); - q->abort(); - return false; - case QPlaylistFileParser::M3U: - m_currentParser.reset(new M3UParser(q)); - break; - case QPlaylistFileParser::M3U8: - m_currentParser.reset(new M3UParser(q)); - m_utf8 = true; - break; - case QPlaylistFileParser::PLS: - m_currentParser.reset(new PLSParser(q)); - break; - } - - Q_ASSERT(!m_currentParser.isNull()); - } - - QString line; - - if (m_utf8) { - line = QString::fromUtf8(m_buffer.constData() + startIndex, length).trimmed(); - } else { - line = QString::fromLatin1(m_buffer.constData() + startIndex, length).trimmed(); - } - if (line.isEmpty()) - return true; - - Q_ASSERT(m_currentParser); - return m_currentParser->parseLine(m_lineIndex, line, m_root); -} - -void QPlaylistFileParserPrivate::handleData() -{ - Q_Q(QPlaylistFileParser); - while (m_stream->bytesAvailable() && !m_aborted) { - int expectedBytes = qMin(READ_LIMIT, int(qMin(m_stream->bytesAvailable(), - qint64(LINE_LIMIT - m_buffer.size())))); - m_buffer.push_back(m_stream->read(expectedBytes)); - int processedBytes = 0; - while (m_scanIndex < m_buffer.length() && !m_aborted) { - char s = m_buffer[m_scanIndex]; - if (s == '\r' || s == '\n') { - int l = m_scanIndex - processedBytes; - if (l > 0) { - if (!processLine(processedBytes, l)) - break; - } - processedBytes = m_scanIndex + 1; - if (!m_stream) { - //some error happened, so exit parsing - return; - } - } - m_scanIndex++; - } - - if (m_aborted) - break; - - if (m_buffer.length() - processedBytes >= LINE_LIMIT) { - emit q->error(QMediaPlaylist::FormatError, QMediaPlaylist::tr("invalid line in playlist file")); - q->abort(); - break; - } - - if (!m_stream->bytesAvailable() && (!m_source || !m_source->isFinished())) { - //last line - processLine(processedBytes, -1); - break; - } - - Q_ASSERT(m_buffer.length() == m_scanIndex); - if (processedBytes == 0) - continue; - - int copyLength = m_buffer.length() - processedBytes; - if (copyLength > 0) { - Q_ASSERT(copyLength <= READ_LIMIT); - m_buffer = m_buffer.right(copyLength); - } else { - m_buffer.clear(); - } - m_scanIndex = 0; - } - - handleParserFinished(); -} - -QPlaylistFileParser::QPlaylistFileParser(QObject *parent) - : QObject(parent) - , d_ptr(new QPlaylistFileParserPrivate(this)) -{ - -} - -QPlaylistFileParser::~QPlaylistFileParser() = default; - -QPlaylistFileParser::FileType QPlaylistFileParser::findByMimeType(const QString &mime) -{ - if (mime == QLatin1String("text/uri-list") || mime == QLatin1String("audio/x-mpegurl") || mime == QLatin1String("audio/mpegurl")) - return QPlaylistFileParser::M3U; - - if (mime == QLatin1String("application/x-mpegURL") || mime == QLatin1String("application/vnd.apple.mpegurl")) - return QPlaylistFileParser::M3U8; - - if (mime == QLatin1String("audio/x-scpls")) - return QPlaylistFileParser::PLS; - - return QPlaylistFileParser::UNKNOWN; -} - -QPlaylistFileParser::FileType QPlaylistFileParser::findBySuffixType(const QString &suffix) -{ - const QString &s = suffix.toLower(); - - if (s == QLatin1String("m3u")) - return QPlaylistFileParser::M3U; - - if (s == QLatin1String("m3u8")) - return QPlaylistFileParser::M3U8; - - if (s == QLatin1String("pls")) - return QPlaylistFileParser::PLS; - - return QPlaylistFileParser::UNKNOWN; -} - -QPlaylistFileParser::FileType QPlaylistFileParser::findByDataHeader(const char *data, quint32 size) -{ - if (!data || size == 0) - return QPlaylistFileParser::UNKNOWN; - - if (size >= 7 && strncmp(data, "#EXTM3U", 7) == 0) - return QPlaylistFileParser::M3U; - - if (size >= 10 && strncmp(data, "[playlist]", 10) == 0) - return QPlaylistFileParser::PLS; - - return QPlaylistFileParser::UNKNOWN; -} - -QPlaylistFileParser::FileType QPlaylistFileParser::findPlaylistType(const QString& suffix, - const QString& mime, - const char *data, - quint32 size) -{ - - FileType dataHeaderType = findByDataHeader(data, size); - if (dataHeaderType != UNKNOWN) - return dataHeaderType; - - FileType mimeType = findByMimeType(mime); - if (mimeType != UNKNOWN) - return mimeType; - - mimeType = findBySuffixType(mime); - if (mimeType != UNKNOWN) - return mimeType; - - FileType suffixType = findBySuffixType(suffix); - if (suffixType != UNKNOWN) - return suffixType; - - return UNKNOWN; -} - -/* - * Delegating - */ -void QPlaylistFileParser::start(const QUrl &media, QIODevice *stream, const QString &mimeType) -{ - if (stream) - start(stream, mimeType); - else - start(media, mimeType); -} - -void QPlaylistFileParser::start(QIODevice *stream, const QString &mimeType) -{ - Q_D(QPlaylistFileParser); - const bool validStream = stream ? (stream->isOpen() && stream->isReadable()) : false; - - if (!validStream) { - Q_EMIT error(QMediaPlaylist::AccessDeniedError, QMediaPlaylist::tr("Invalid stream")); - return; - } - - if (!d->m_currentParser.isNull()) { - abort(); - d->m_pendingJob = { stream, QUrl(), mimeType }; - return; - } - - playlist.clear(); - d->reset(); - d->m_mimeType = mimeType; - d->m_stream = stream; - connect(d->m_stream, SIGNAL(readyRead()), this, SLOT(handleData())); - d->handleData(); -} - -void QPlaylistFileParser::start(const QUrl& request, const QString &mimeType) -{ - Q_D(QPlaylistFileParser); - const QUrl &url = request.url(); - - if (url.isLocalFile() && !QFile::exists(url.toLocalFile())) { - emit error(QMediaPlaylist::AccessDeniedError, QString(QMediaPlaylist::tr("%1 does not exist")).arg(url.toString())); - return; - } - - if (!d->m_currentParser.isNull()) { - abort(); - d->m_pendingJob = { nullptr, request, mimeType }; - return; - } - - d->reset(); - d->m_root = url; - d->m_mimeType = mimeType; - d->m_source.reset(d->m_mgr.get(QNetworkRequest(request))); - d->m_stream = d->m_source.get(); - connect(d->m_source.data(), SIGNAL(readyRead()), this, SLOT(handleData())); - connect(d->m_source.data(), SIGNAL(finished()), this, SLOT(handleData())); - connect(d->m_source.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(handleError())); - - if (url.isLocalFile()) - d->handleData(); -} - -void QPlaylistFileParser::abort() -{ - Q_D(QPlaylistFileParser); - d->abort(); - - if (d->m_source) - d->m_source->disconnect(); - - if (d->m_stream) - disconnect(d->m_stream, SIGNAL(readyRead()), this, SLOT(handleData())); - - playlist.clear(); -} - -void QPlaylistFileParser::handleData() -{ - Q_D(QPlaylistFileParser); - d->handleData(); -} - -void QPlaylistFileParserPrivate::handleParserFinished() -{ - Q_Q(QPlaylistFileParser); - const bool isParserValid = !m_currentParser.isNull(); - if (!isParserValid && !m_aborted) - emit q->error(QMediaPlaylist::FormatNotSupportedError, QMediaPlaylist::tr("Empty file provided")); - - if (isParserValid && !m_aborted) { - m_currentParser.reset(); - emit q->finished(); - } - - if (!m_aborted) - q->abort(); - - if (!m_source.isNull()) - m_source.reset(); - - if (m_pendingJob.isValid()) - q->start(m_pendingJob.m_media, m_pendingJob.m_stream, m_pendingJob.m_mimeType); -} - -void QPlaylistFileParserPrivate::abort() -{ - m_aborted = true; - if (!m_currentParser.isNull()) - m_currentParser->abort(); -} - -void QPlaylistFileParserPrivate::reset() -{ - Q_ASSERT(m_currentParser.isNull()); - Q_ASSERT(m_source.isNull()); - m_buffer.clear(); - m_root.clear(); - m_mimeType.clear(); - m_stream = nullptr; - m_type = QPlaylistFileParser::UNKNOWN; - m_scanIndex = 0; - m_lineIndex = -1; - m_utf8 = false; - m_aborted = false; - m_pendingJob.reset(); -} - -void QPlaylistFileParser::handleError() -{ - Q_D(QPlaylistFileParser); - const QString &errorString = d->m_source->errorString(); - Q_EMIT error(QMediaPlaylist::NetworkError, errorString); - abort(); -} - -QT_END_NAMESPACE diff --git a/qt/qplaylistfileparser_p.h b/qt/qplaylistfileparser_p.h deleted file mode 100644 index 3d20167..0000000 --- a/qt/qplaylistfileparser_p.h +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef PLAYLISTFILEPARSER_P_H -#define PLAYLISTFILEPARSER_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "qtmultimediaglobal.h" -#include "qmediaplaylist.h" -#include - -QT_BEGIN_NAMESPACE - -class QIODevice; -class QUrl; -class QNetworkRequest; - -class QPlaylistFileParserPrivate; - -class QPlaylistFileParser : public QObject -{ - Q_OBJECT -public: - QPlaylistFileParser(QObject *parent = nullptr); - ~QPlaylistFileParser(); - - enum FileType - { - UNKNOWN, - M3U, - M3U8, // UTF-8 version of M3U - PLS - }; - - void start(const QUrl &media, QIODevice *stream = nullptr, const QString &mimeType = QString()); - void start(const QUrl &request, const QString &mimeType = QString()); - void start(QIODevice *stream, const QString &mimeType = QString()); - void abort(); - - QList playlist; - -Q_SIGNALS: - void newItem(const QVariant& content); - void finished(); - void error(QMediaPlaylist::Error err, const QString& errorMsg); - -private Q_SLOTS: - void handleData(); - void handleError(); - -private: - - static FileType findByMimeType(const QString &mime); - static FileType findBySuffixType(const QString &suffix); - static FileType findByDataHeader(const char *data, quint32 size); - static FileType findPlaylistType(QIODevice *device, - const QString& mime); - static FileType findPlaylistType(const QString &suffix, - const QString& mime, - const char *data = nullptr, - quint32 size = 0); - - Q_DISABLE_COPY(QPlaylistFileParser) - Q_DECLARE_PRIVATE(QPlaylistFileParser) - QScopedPointer d_ptr; -}; - -QT_END_NAMESPACE - -#endif // PLAYLISTFILEPARSER_P_H diff --git a/resources.qrc b/resources.qrc index 34086bf..0a8fa44 100644 --- a/resources.qrc +++ b/resources.qrc @@ -15,6 +15,7 @@ icons/media-playlist-shuffle.png icons/media-playlist-repeat-song.png icons/media-playlist-normal.png + icons/media-repeat-single.png icons/media-album-cover.svg