221 lines
12 KiB
JavaScript
221 lines
12 KiB
JavaScript
const config = {
|
|
host: 'http://118.81.247.67:2345',/*Emby服务器URL (例如 http://localhost:8096)*/
|
|
userId: "40a02f8503ce4de49d58331a282dcea1",/*Emby_userId (通过账号密码获取)*/
|
|
token: "8773348f379f4ddeb0244f7edee289fe",/*Emby_token (通过账号密码获取)*/
|
|
deviceId: "ea27caf7-9a51-4209-b1a5-374bf30c2ffd",
|
|
clientVersion: "4.9.0.31"
|
|
};
|
|
|
|
const deviceProfile = { DeviceProfile: { MaxStaticBitrate: 140000000, MaxStreamingBitrate: 140000000, MusicStreamingTranscodingBitrate: 192000, DirectPlayProfiles: [{ Container: "mp4,m4v", Type: "Video", VideoCodec: "h264,h265,hevc,av1,vp8,vp9", AudioCodec: "ac3,eac3,mp3,aac,opus,flac,vorbis" }, { Container: "mkv", Type: "Video", VideoCodec: "h264,h265,hevc,av1,vp8,vp9", AudioCodec: "ac3,eac3,mp3,aac,opus,flac,vorbis" }, { Container: "flv", Type: "Video", VideoCodec: "h264", AudioCodec: "aac,mp3" }, { Container: "mov", Type: "Video", VideoCodec: "h264", AudioCodec: "ac3,eac3,mp3,aac,opus,flac,vorbis" }, { Container: "opus", Type: "Audio" }, { Container: "mp3", Type: "Audio", AudioCodec: "mp3" }, { Container: "mp2,mp3", Type: "Audio", AudioCodec: "mp2" }, { Container: "aac", Type: "Audio", AudioCodec: "aac" }, { Container: "m4a", AudioCodec: "aac", Type: "Audio" }, { Container: "mp4", AudioCodec: "aac", Type: "Audio" }, { Container: "flac", Type: "Audio" }, { Container: "webma,webm", Type: "Audio" }, { Container: "wav", Type: "Audio", AudioCodec: "PCM_S16LE,PCM_S24LE" }, { Container: "ogg", Type: "Audio" }, { Container: "webm", Type: "Video", AudioCodec: "vorbis,opus", VideoCodec: "av1,VP8,VP9" }], TranscodingProfiles: [{ Container: "aac", Type: "Audio", AudioCodec: "aac", Context: "Streaming", Protocol: "hls", MaxAudioChannels: "2", MinSegments: "1", BreakOnNonKeyFrames: true }, { Container: "aac", Type: "Audio", AudioCodec: "aac", Context: "Streaming", Protocol: "http", MaxAudioChannels: "2" }, { Container: "mp3", Type: "Audio", AudioCodec: "mp3", Context: "Streaming", Protocol: "http", MaxAudioChannels: "2" }, { Container: "opus", Type: "Audio", AudioCodec: "opus", Context: "Streaming", Protocol: "http", MaxAudioChannels: "2" }, { Container: "wav", Type: "Audio", AudioCodec: "wav", Context: "Streaming", Protocol: "http", MaxAudioChannels: "2" }, { Container: "opus", Type: "Audio", AudioCodec: "opus", Context: "Static", Protocol: "http", MaxAudioChannels: "2" }, { Container: "mp3", Type: "Audio", AudioCodec: "mp3", Context: "Static", Protocol: "http", MaxAudioChannels: "2" }, { Container: "aac", Type: "Audio", AudioCodec: "aac", Context: "Static", Protocol: "http", MaxAudioChannels: "2" }, { Container: "wav", Type: "Audio", AudioCodec: "wav", Context: "Static", Protocol: "http", MaxAudioChannels: "2" }, { Container: "ts", Type: "Video", VideoCodec: "h264", AudioCodec: "aac,mp3", Protocol: "hls", MaxAudioChannels: "2", MinSegments: "1", BreakOnNonKeyFrames: true, Context: "Streaming" }, { Container: "mp4", Type: "Video", VideoCodec: "h264", AudioCodec: "aac,mp3", Protocol: "http", MaxAudioChannels: "2", Context: "Static" }, { Container: "webm", Type: "Video", VideoCodec: "vp8", AudioCodec: "vorbis", Protocol: "http", MaxAudioChannels: "2", Context: "Static" }, { Container: "webm", Type: "Video", VideoCodec: "vp8", AudioCodec: "vorbis", Protocol: "http", MaxAudioChannels: "2", Context: "Streaming" }], SubtitleProfiles: [{ Format: "ass", Method: "External" }, { Format: "ssa", Method: "External" }, { Format: "srt", Method: "External" }, { Format: "subrip", Method: "External" }, { Format: "vtt", Method: "External" }, { Format: "pgssub", Method: "Embed" }, { Format: "pgs", Method: "Embed" }, { Format: "dvdsub", Method: "Embed" }, { Format: "vobsub", Method: "Embed" }, { Format: "subrip", Method: "Embed" }, { Format: "ass", Method: "Embed" }, { Format: "ssa", Method: "Embed" }], CodecProfiles: [{ Type: "Video", Codec: "h264", ApplyConditions: [{ Condition: "LessThanEqual", Property: "VideoLevel", Value: "62", IsRequired: false }] }, { Type: "Video", Codec: "h265", ApplyConditions: [{ Condition: "LessThanEqual", Property: "VideoLevel", Value: "153", IsRequired: false }] }], ResponseProfiles: [{ Container: "m4v", Type: "Video", MimeType: "video/mp4" }], ContainerProfiles: [], LiveStreamProfiles: ["m3u8"], SupportedCommands: ["Play", "Pause", "Stop", "Seek", "SetAudioStreamIndex", "SetSubtitleStreamIndex"], IgnoreDts: true, IgnoreIndex: false, MinSegments: 0, BreakOnNonKeyFrames: true, TranscodeReasons: ["ContainerNotSupported", "VideoCodecNotSupported", "AudioCodecNotSupported"] } };
|
|
const getHeaders = (extra = {}) => ({
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
"Referer": config.host + "/",
|
|
"Accept-Language": "zh-CN,zh;q=0.9",
|
|
"X-Emby-Client": "Emby Web",
|
|
"X-Emby-Device-Name": "Android WebView Android",
|
|
"X-Emby-Device-Id": config.deviceId,
|
|
"X-Emby-Client-Version": config.clientVersion,
|
|
"X-Emby-Token": config.token,
|
|
...extra
|
|
});
|
|
const buildUrl = (endpoint, params = {}) => {
|
|
const headers = getHeaders();
|
|
const baseParams = {};
|
|
for (const [key, value] of Object.entries(headers)) {
|
|
if (key.startsWith('X-Emby-')) {
|
|
baseParams[key] = value;
|
|
}
|
|
}
|
|
baseParams['X-Emby-Language'] = 'zh-cn';
|
|
const allParams = { ...baseParams, ...params };
|
|
const queryString = Object.entries(allParams)
|
|
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
|
|
.join('&');
|
|
return `${config.host}${endpoint}${endpoint.includes('?') ? '&' : '?'}${queryString}`;
|
|
};
|
|
const getImageUrl = (itemId, imageTag) =>
|
|
imageTag ? `${config.host}/emby/Items/${itemId}/Images/Primary?maxWidth=400&tag=${imageTag}&quality=90` : "";
|
|
const extractVideos = (jsonData) => {
|
|
return (jsonData?.Items || []).map(it => ({
|
|
vod_id: it.Id,
|
|
vod_name: it.Name || "",
|
|
vod_pic: getImageUrl(it.Id, it.ImageTags?.Primary),
|
|
vod_remarks: it.ProductionYear?.toString() || ""
|
|
}));
|
|
};
|
|
const fetchApi = async (url, options = {}) => {
|
|
const resp = await req(url, options);
|
|
return resp?.content ? JSON.parse(resp.content) : null;
|
|
};
|
|
const getViews = async () => {
|
|
const url = buildUrl(`/emby/Users/${config.userId}/Views`);
|
|
return await fetchApi(url, { headers: getHeaders() });
|
|
};
|
|
const homeVod = async () => {
|
|
const url = buildUrl(`/emby/Users/${config.userId}/Items`, {
|
|
SortBy: 'DateCreated',
|
|
SortOrder: 'Descending',
|
|
IncludeItemTypes: 'Movie,Series',
|
|
Recursive: 'true',
|
|
Limit: 40,
|
|
Fields: 'BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,CommunityRating,Status,CriticRating,EndDate,Path,Overview',
|
|
EnableImageTypes: 'Primary,Backdrop,Thumb,Banner',
|
|
ImageTypeLimit: 1
|
|
});
|
|
const json = await fetchApi(url, { headers: getHeaders() });
|
|
return JSON.stringify({
|
|
list: extractVideos(json)
|
|
});
|
|
};
|
|
const home = async () => {
|
|
const json = await getViews();
|
|
const classList = (json?.Items || [])
|
|
.filter(it => !it.Name.includes("播放列表") && !it.Name.includes("相机"))
|
|
.map(it => ({
|
|
type_id: it.Id,
|
|
type_name: it.Name
|
|
}));
|
|
return JSON.stringify({
|
|
class: classList,
|
|
filters: {},
|
|
list: []
|
|
});
|
|
};
|
|
const category = async (tid, pg, _, extend) => {
|
|
const startIndex = (pg - 1) * 30;
|
|
const url = buildUrl(`/emby/Users/${config.userId}/Items`, {
|
|
SortBy: 'DateLastContentAdded,SortName',
|
|
SortOrder: 'Descending',
|
|
IncludeItemTypes: 'Movie,Series',
|
|
Recursive: 'true',
|
|
Fields: 'BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,CommunityRating,Status,CriticRating,EndDate,Path',
|
|
StartIndex: startIndex,
|
|
ParentId: tid,
|
|
EnableImageTypes: 'Primary,Backdrop,Thumb,Banner',
|
|
ImageTypeLimit: 1,
|
|
Limit: 30,
|
|
EnableUserData: 'true'
|
|
});
|
|
const json = await fetchApi(url, { headers: getHeaders() });
|
|
const list = extractVideos(json);
|
|
const total = json?.TotalRecordCount || 0;
|
|
const pagecount = pg * 30 < total ? +pg + 1 : +pg;
|
|
return JSON.stringify({
|
|
list,
|
|
page: +pg,
|
|
pagecount,
|
|
limit: 30,
|
|
total
|
|
});
|
|
};
|
|
const detail = async (id) => {
|
|
const info = await fetchApi(buildUrl(`/emby/Users/${config.userId}/Items/${id}`), { headers: getHeaders() });
|
|
|
|
const VOD = {
|
|
vod_id: id,
|
|
vod_name: info?.Name || "",
|
|
vod_pic: getImageUrl(id, info?.ImageTags?.Primary),
|
|
vod_content: info?.Overview?.replace(/\xa0/g, ' ').replace(/\n\n/g, '\n').trim() || "暂无简介",
|
|
vod_year: info?.ProductionYear?.toString() || "",
|
|
vod_director: "",
|
|
vod_actor: "",
|
|
vod_type: info?.Genres?.join(" / ") || "",
|
|
vod_play_from: "",
|
|
vod_play_url: ""
|
|
};
|
|
if (!info) return JSON.stringify({ list: [VOD] });
|
|
if (info.Type === "Series") {
|
|
const seasons = await fetchApi(buildUrl(`/emby/Shows/${id}/Seasons`, {
|
|
UserId: config.userId,
|
|
Fields: 'BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,CommunityRating,Status,CriticRating,EndDate,Path,Overview',
|
|
EnableTotalRecordCount: 'false'
|
|
}), { headers: getHeaders() });
|
|
const from = [];
|
|
const result = [];
|
|
for (const season of seasons?.Items || []) {
|
|
from.push(season.Name);
|
|
const episodes = await fetchApi(buildUrl(`/emby/Shows/${id}/Episodes`, {
|
|
SeasonId: season.Id,
|
|
ImageTypeLimit: 1,
|
|
UserId: config.userId,
|
|
Fields: 'Overview,PrimaryImageAspectRatio',
|
|
Limit: 1000
|
|
}), { headers: getHeaders() });
|
|
if (episodes?.Items) {
|
|
const playlist = episodes.Items.map(item => `${item.Name}$${item.Id}`);
|
|
result.push(playlist.join("#"));
|
|
}
|
|
}
|
|
VOD.vod_play_from = from.join("$$$");
|
|
VOD.vod_play_url = result.join("$$$");
|
|
} else if (!info.IsFolder) {
|
|
VOD.vod_play_from = "Emby";
|
|
VOD.vod_play_url = `${info.Name || "播放"}$${id}`;
|
|
} else {
|
|
const items = await fetchApi(buildUrl(`/emby/Users/${config.userId}/Items`, {
|
|
ParentId: id,
|
|
Fields: 'BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,CommunityRating,CriticRating',
|
|
ImageTypeLimit: 1,
|
|
StartIndex: 0,
|
|
EnableUserData: 'true'
|
|
}), { headers: getHeaders() });
|
|
if (items?.Items) {
|
|
const playlist = items.Items.map(item => `${item.Name.replace(/#/g, '-').replace(/\$/g, '|').trim()}$${item.Id}`);
|
|
VOD.vod_play_from = "Emby";
|
|
VOD.vod_play_url = playlist.join("#");
|
|
}
|
|
}
|
|
return JSON.stringify({
|
|
list: [VOD]
|
|
});
|
|
};
|
|
const search = async (wd, _, pg = 1) => {
|
|
const url = buildUrl(`/emby/Users/${config.userId}/Items`, {
|
|
SortBy: 'SortName',
|
|
SortOrder: 'Ascending',
|
|
Fields: 'BasicSyncInfo,CanDelete,Container,PrimaryImageAspectRatio,ProductionYear,Status,EndDate',
|
|
StartIndex: (pg - 1) * 50,
|
|
EnableImageTypes: 'Primary,Backdrop,Thumb',
|
|
ImageTypeLimit: 1,
|
|
Recursive: 'true',
|
|
SearchTerm: wd,
|
|
GroupProgramsBySeries: 'true',
|
|
Limit: 50
|
|
});
|
|
const json = await fetchApi(url, { headers: getHeaders() });
|
|
return JSON.stringify({ list: extractVideos(json) });
|
|
};
|
|
const play = async (_, id) => {
|
|
const url = buildUrl(`/emby/Items/${id}/PlaybackInfo`, {
|
|
UserId: config.userId,
|
|
IsPlayback: 'false',
|
|
AutoOpenLiveStream: 'false',
|
|
StartTimeTicks: 0,
|
|
MaxStreamingBitrate: 2147483647
|
|
});
|
|
const reqHeaders = getHeaders({
|
|
"Content-Type": "application/json"
|
|
});
|
|
const json = await fetchApi(url, {
|
|
method: "POST",
|
|
headers: reqHeaders,
|
|
body: JSON.stringify(deviceProfile)
|
|
});
|
|
const mediaSource = json?.MediaSources?.[0];
|
|
const playUrl = mediaSource?.DirectStreamUrl || mediaSource?.DirectPlayUrl;
|
|
if (!playUrl) {
|
|
return JSON.stringify({
|
|
parse: 1,
|
|
url,
|
|
header: reqHeaders,
|
|
msg: "无法获取播放URL"
|
|
});
|
|
}
|
|
return JSON.stringify({
|
|
parse: 0,
|
|
url: config.host + playUrl,
|
|
header: reqHeaders
|
|
});
|
|
};
|
|
export default { home, homeVod, category, detail, search, play };
|