「iOS&macOS语音输入法」在Spokenly中使用cf worker连接到百炼,让语音输入更顺畅
- 内容介绍
- 文章标签
- 相关推荐
20250912172237 引入重要更新 为了让更多佬友体验到 Qwen3-ASR + Spokenly,目前帖子将不设置等级权限。 免费使用的两个方法: 【路径一】通过阿里云百炼官方赠额使用 将下方的代码粘贴到 Cloudflare Worker 中,点击部署 访问百炼控制台 https://bailian.console.aliyun.com/ ,新建 API Key,将Key粘贴到 …
看到大佬的帖子,在原帖中存在一些对于性能可靠性的讨论
其实可以在原始worker代码中替换为新加坡的接入点,这样能让整个数据路径更优
(当然我默认大家都有一个很好的网络到worker)
原始代码中路径为:User→CF Worker→BaiLian Beijing,很多时候,用户卡在CF Worker→BaiLian Beijing这段路径而不自知
简单修改接入点为新加坡百炼接入点的优势:
image950×566 58 KB
User→CF Worker→BaiLian Singapore,此时CF Worker→BaiLian Singapore一般不会有问题,推荐要么直连CF Worker或者使用新加坡/香港的vm链接到CF worker这样能令整体的链路最短和最优
至于费用问题:
北京百炼的价格是0.00022每秒
坡县百炼的价格是0.00026每秒
一般是薅各种优惠券,我试过了可以卷也可以抵扣国际版本,当然花钱用的话其实价格也非不可接受
模型均可食用原帖建议,我用的是qwen3-asr-flash:itn
以下代码比起原帖仅修改了接入点,复制粘贴到worker部署即可享用
推荐在Spokenly使用的接入点为
https://你的worker地址/v1/audio/transcriptions
// Upstream ASR endpoint (editable default)
const DEFAULT_UPSTREAM_ASR_ENDPOINT = "{proxy-url}";
// CORS response helpers
function corsHeaders() {
return {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};
}
function withCors(res) {
const h = new Headers(res.headers);
for (const [k, v] of Object.entries(corsHeaders())) h.set(k, v);
return new Response(res.body, { status: res.status, headers: h });
}
function ok(text, contentType = "text/plain; charset=utf-8") {
return withCors(new Response(text, { status: 200, headers: { "Content-Type": contentType } }));
}
function json(data, status = 200) {
return withCors(new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json" } }));
}
function badRequest(message) {
return json({ error: message }, 400);
}
// Efficient base64 encoding using chunks to avoid quadratic string concatenation
async function encodeBase64(ab) {
const bytes = new Uint8Array(ab);
const chunk = 0x8000;
const parts = [];
for (let i = 0; i < bytes.length; i += chunk) {
const sub = bytes.subarray(i, i + chunk);
parts.push(String.fromCharCode(...sub));
}
const binary = parts.join("");
return btoa(binary);
}
// Determine MIME type from file extension
function mimeFromName(name, fallback = "application/octet-stream") {
const dot = name.lastIndexOf(".");
if (dot < 0) return fallback;
const ext = name.slice(dot + 1).toLowerCase();
switch (ext) {
case "mp3": return "audio/mpeg";
case "wav": return "audio/wav";
case "m4a": return "audio/mp4";
case "flac": return "audio/flac";
case "ogg":
case "oga": return "audio/ogg";
case "webm":
case "weba": return "audio/webm";
default: return fallback;
}
}
// DashScope transcription handler
async function handleDashscope({ file, language, prompt, modelRaw, enableITN, dashKey }) {
// 解析模型,默认 qwen3-asr-flash,并去掉 :itn 后缀
const model = (modelRaw || "").replace(/:itn$/i, "") || "qwen3-asr-flash";
// 1) 获取临时上传策略
const policyResp = await fetch(
"https://dashscope-intl.aliyuncs.com/api/v1/uploads?action=getPolicy&model=" + encodeURIComponent(model),
{
method: "GET",
headers: {
"Authorization": `Bearer ${dashKey}`,
"Content-Type": "application/json",
},
},
);
if (!policyResp.ok) {
return json({ error: "getPolicy failed", detail: await policyResp.text() }, 502);
}
const policyJSON = await policyResp.json().catch(async () => ({ error: await policyResp.text() }));
const policy = policyJSON?.data;
if (!policy) {
return json({ error: "invalid getPolicy response", detail: policyJSON }, 502);
}
// 2) 上传文件到临时 OSS
const uploadDir = (policy.upload_dir || "").replace(/\/+$/, "");
const key = uploadDir ? `${uploadDir}/${file.name || "upload"}` : (file.name || "upload");
const ossForm = new FormData();
ossForm.set("OSSAccessKeyId", policy.oss_access_key_id);
ossForm.set("Signature", policy.signature);
ossForm.set("policy", policy.policy);
if (policy.x_oss_object_acl) ossForm.set("x-oss-object-acl", policy.x_oss_object_acl);
if (policy.x_oss_forbid_overwrite) ossForm.set("x-oss-forbid-overwrite", policy.x_oss_forbid_overwrite);
if (policy.x_oss_security_token) ossForm.set("x-oss-security-token", policy.x_oss_security_token);
ossForm.set("key", key);
ossForm.set("success_action_status", "200");
ossForm.set("file", file, file.name || "upload");
const ossResp = await fetch(policy.upload_host, { method: "POST", body: ossForm });
if (!ossResp.ok) {
return json({ error: "OSS upload failed", detail: await ossResp.text() }, 502);
}
const ossUrl = `oss://${key}`;
// 3) 调用 DashScope ASR
const asrOptions = {
// 语言识别默认开启
enable_lid: true,
// ITN 默认关闭,若启用则置 true
enable_itn: false,
...(language !== "auto" ? { language } : {}),
};
if (enableITN) asrOptions.enable_itn = true;
const body = {
model,
input: {
messages: [
{ role: "system", content: [{ text: prompt || "" }] },
{ role: "user", content: [{ audio: ossUrl }] },
],
},
parameters: {
asr_options: asrOptions,
},
};
const asrResp = await fetch(
"https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation",
{
method: "POST",
headers: {
"Authorization": `Bearer ${dashKey}`,
"Content-Type": "application/json",
"X-DashScope-OssResourceResolve": "enable",
},
body: JSON.stringify(body),
},
);
const asrJSON = await asrResp.json().catch(async () => ({ error: await asrResp.text() }));
if (!asrResp.ok) return json({ error: "ASR not ok", detail: asrJSON }, 502);
const msg = asrJSON?.output?.choices?.[0]?.message;
const text = Array.isArray(msg?.content) ? (msg.content.find((x) => x?.text)?.text || "") : "";
return json({ text }, 200);
}
export default {
async fetch(request, env) {
// Preflight
if (request.method === "OPTIONS") {
return withCors(new Response(null, { status: 204 }));
}
const url = new URL(request.url);
if (url.pathname === "/healthz") {
return ok("ok");
}
if (url.pathname === "/v1/audio/transcriptions") {
if (request.method !== "POST") return badRequest("method must be POST");
// Parse multipart form
let form;
try {
form = await request.formData();
} catch (e) {
return badRequest(`failed to parse multipart form: ${String(e?.message || e)}`);
}
const file = form.get("file");
if (!(file instanceof File)) {
return badRequest("missing required file field");
}
const language = form.get("language")?.toString() || "auto";
const prompt = form.get("prompt")?.toString() || "";
const modelRaw = form.get("model")?.toString() || "";
const enableITN = (() => {
const m = modelRaw.trim().toLowerCase();
return m === ":itn" || m.endsWith(":itn");
})();
// 路由判定:存在 Bearer token 则走 DashScope,否则走 Z.ai
const auth = request.headers.get("Authorization");
const dashKey = auth && auth.startsWith("Bearer ") ? auth.slice(7).trim() : "";
if (dashKey) {
return await handleDashscope({ file, language, prompt, modelRaw, enableITN, dashKey });
}
// Read file and encode to base64
let b64 = "";
let sizeBytes = 0;
try {
const ab = await file.arrayBuffer();
sizeBytes = ab.byteLength;
b64 = await encodeBase64(ab);
} catch (e) {
return json({ error: `failed to read file: ${String(e?.message || e)}` }, 500);
}
const upstream = (env && env.UPSTREAM_ASR_ENDPOINT) || DEFAULT_UPSTREAM_ASR_ENDPOINT;
const payload = {
audio_file: {
data: b64,
name: file.name || "upload",
type: (file.type && file.type !== "application/octet-stream") ? file.type : mimeFromName(file.name || ""),
size: (typeof file.size === "number" && file.size >= 0) ? file.size : sizeBytes,
},
language,
};
const context = prompt.trim();
if (context) payload.context = context;
if (enableITN) payload.enable_itn = true;
let upResp;
try {
upResp = await fetch(upstream, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
} catch (e) {
return json({ error: `upstream request failed: ${String(e?.message || e)}` }, 502);
}
// Parse upstream response efficiently
const ct = upResp.headers.get("content-type") || "";
let upJSON;
if (ct.includes("application/json")) {
try {
upJSON = await upResp.json();
} catch (e) {
// Fallback to text for error details
const fallbackText = await upResp.text();
return json({ error: "invalid upstream json", detail: fallbackText }, 502);
}
if (!upResp.ok) {
return json({ error: "upstream not ok", detail: upJSON }, 502);
}
} else {
const upText = await upResp.text();
if (!upResp.ok) {
return json({ error: "upstream not ok", detail: upText }, 502);
}
try {
upJSON = JSON.parse(upText);
} catch {
return json({ error: "invalid upstream response", detail: upText }, 502);
}
}
if (upJSON && upJSON.success === false) {
return json(upJSON, 502);
}
const data = Array.isArray(upJSON?.data) ? upJSON.data : [];
const recognizedText = data[0] || "";
return json({ text: recognizedText }, 200);
}
return json({ error: "not found" }, 404);
},
};
网友解答:
--【壹】--:
不太想回答这个问题,贫道只能告诉你咸鱼搜索阿里云 300
祝你用的开心
--【贰】--:
强呀,大佬
--【叁】--:
牛哇!!!感谢分享!!
--【肆】--:
如果你到CF的网络存在性能问题的话,我的这个改进方法并不会让你用起来更顺畅
我建议你可以用香港/新加坡的节点并且确保你能到节点的网络是《24小时的顺畅》这样用起来才能得到很好的解决
--【伍】--:
弱弱问下,有试过的大佬效果改善明显吗?目前我是在cf上搭建的转换服务,实际使用上,手机端的延迟几乎没法用,macOS端在本地搭建的转换服务,倒是很快。感觉更多的是cf的往返节点的延迟更大吧?
--【陆】--:
谢谢,我用量不多,看了这个三百有效期一年,给我用浪费了,还是按量付费就行。
--【柒】--:
非常感谢,测试目前用上了新加坡,速度快多了。就是没赠送时长,请问有优惠的充值方式吗?闲鱼上没看到优惠券之类的。
--【捌】--:
并不强,做出脚本的大佬比较强 我只是对脚本中可能导致性能问题的点提出修正
20250912172237 引入重要更新 为了让更多佬友体验到 Qwen3-ASR + Spokenly,目前帖子将不设置等级权限。 免费使用的两个方法: 【路径一】通过阿里云百炼官方赠额使用 将下方的代码粘贴到 Cloudflare Worker 中,点击部署 访问百炼控制台 https://bailian.console.aliyun.com/ ,新建 API Key,将Key粘贴到 …
看到大佬的帖子,在原帖中存在一些对于性能可靠性的讨论
其实可以在原始worker代码中替换为新加坡的接入点,这样能让整个数据路径更优
(当然我默认大家都有一个很好的网络到worker)
原始代码中路径为:User→CF Worker→BaiLian Beijing,很多时候,用户卡在CF Worker→BaiLian Beijing这段路径而不自知
简单修改接入点为新加坡百炼接入点的优势:
image950×566 58 KB
User→CF Worker→BaiLian Singapore,此时CF Worker→BaiLian Singapore一般不会有问题,推荐要么直连CF Worker或者使用新加坡/香港的vm链接到CF worker这样能令整体的链路最短和最优
至于费用问题:
北京百炼的价格是0.00022每秒
坡县百炼的价格是0.00026每秒
一般是薅各种优惠券,我试过了可以卷也可以抵扣国际版本,当然花钱用的话其实价格也非不可接受
模型均可食用原帖建议,我用的是qwen3-asr-flash:itn
以下代码比起原帖仅修改了接入点,复制粘贴到worker部署即可享用
推荐在Spokenly使用的接入点为
https://你的worker地址/v1/audio/transcriptions
// Upstream ASR endpoint (editable default)
const DEFAULT_UPSTREAM_ASR_ENDPOINT = "{proxy-url}";
// CORS response helpers
function corsHeaders() {
return {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};
}
function withCors(res) {
const h = new Headers(res.headers);
for (const [k, v] of Object.entries(corsHeaders())) h.set(k, v);
return new Response(res.body, { status: res.status, headers: h });
}
function ok(text, contentType = "text/plain; charset=utf-8") {
return withCors(new Response(text, { status: 200, headers: { "Content-Type": contentType } }));
}
function json(data, status = 200) {
return withCors(new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json" } }));
}
function badRequest(message) {
return json({ error: message }, 400);
}
// Efficient base64 encoding using chunks to avoid quadratic string concatenation
async function encodeBase64(ab) {
const bytes = new Uint8Array(ab);
const chunk = 0x8000;
const parts = [];
for (let i = 0; i < bytes.length; i += chunk) {
const sub = bytes.subarray(i, i + chunk);
parts.push(String.fromCharCode(...sub));
}
const binary = parts.join("");
return btoa(binary);
}
// Determine MIME type from file extension
function mimeFromName(name, fallback = "application/octet-stream") {
const dot = name.lastIndexOf(".");
if (dot < 0) return fallback;
const ext = name.slice(dot + 1).toLowerCase();
switch (ext) {
case "mp3": return "audio/mpeg";
case "wav": return "audio/wav";
case "m4a": return "audio/mp4";
case "flac": return "audio/flac";
case "ogg":
case "oga": return "audio/ogg";
case "webm":
case "weba": return "audio/webm";
default: return fallback;
}
}
// DashScope transcription handler
async function handleDashscope({ file, language, prompt, modelRaw, enableITN, dashKey }) {
// 解析模型,默认 qwen3-asr-flash,并去掉 :itn 后缀
const model = (modelRaw || "").replace(/:itn$/i, "") || "qwen3-asr-flash";
// 1) 获取临时上传策略
const policyResp = await fetch(
"https://dashscope-intl.aliyuncs.com/api/v1/uploads?action=getPolicy&model=" + encodeURIComponent(model),
{
method: "GET",
headers: {
"Authorization": `Bearer ${dashKey}`,
"Content-Type": "application/json",
},
},
);
if (!policyResp.ok) {
return json({ error: "getPolicy failed", detail: await policyResp.text() }, 502);
}
const policyJSON = await policyResp.json().catch(async () => ({ error: await policyResp.text() }));
const policy = policyJSON?.data;
if (!policy) {
return json({ error: "invalid getPolicy response", detail: policyJSON }, 502);
}
// 2) 上传文件到临时 OSS
const uploadDir = (policy.upload_dir || "").replace(/\/+$/, "");
const key = uploadDir ? `${uploadDir}/${file.name || "upload"}` : (file.name || "upload");
const ossForm = new FormData();
ossForm.set("OSSAccessKeyId", policy.oss_access_key_id);
ossForm.set("Signature", policy.signature);
ossForm.set("policy", policy.policy);
if (policy.x_oss_object_acl) ossForm.set("x-oss-object-acl", policy.x_oss_object_acl);
if (policy.x_oss_forbid_overwrite) ossForm.set("x-oss-forbid-overwrite", policy.x_oss_forbid_overwrite);
if (policy.x_oss_security_token) ossForm.set("x-oss-security-token", policy.x_oss_security_token);
ossForm.set("key", key);
ossForm.set("success_action_status", "200");
ossForm.set("file", file, file.name || "upload");
const ossResp = await fetch(policy.upload_host, { method: "POST", body: ossForm });
if (!ossResp.ok) {
return json({ error: "OSS upload failed", detail: await ossResp.text() }, 502);
}
const ossUrl = `oss://${key}`;
// 3) 调用 DashScope ASR
const asrOptions = {
// 语言识别默认开启
enable_lid: true,
// ITN 默认关闭,若启用则置 true
enable_itn: false,
...(language !== "auto" ? { language } : {}),
};
if (enableITN) asrOptions.enable_itn = true;
const body = {
model,
input: {
messages: [
{ role: "system", content: [{ text: prompt || "" }] },
{ role: "user", content: [{ audio: ossUrl }] },
],
},
parameters: {
asr_options: asrOptions,
},
};
const asrResp = await fetch(
"https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation",
{
method: "POST",
headers: {
"Authorization": `Bearer ${dashKey}`,
"Content-Type": "application/json",
"X-DashScope-OssResourceResolve": "enable",
},
body: JSON.stringify(body),
},
);
const asrJSON = await asrResp.json().catch(async () => ({ error: await asrResp.text() }));
if (!asrResp.ok) return json({ error: "ASR not ok", detail: asrJSON }, 502);
const msg = asrJSON?.output?.choices?.[0]?.message;
const text = Array.isArray(msg?.content) ? (msg.content.find((x) => x?.text)?.text || "") : "";
return json({ text }, 200);
}
export default {
async fetch(request, env) {
// Preflight
if (request.method === "OPTIONS") {
return withCors(new Response(null, { status: 204 }));
}
const url = new URL(request.url);
if (url.pathname === "/healthz") {
return ok("ok");
}
if (url.pathname === "/v1/audio/transcriptions") {
if (request.method !== "POST") return badRequest("method must be POST");
// Parse multipart form
let form;
try {
form = await request.formData();
} catch (e) {
return badRequest(`failed to parse multipart form: ${String(e?.message || e)}`);
}
const file = form.get("file");
if (!(file instanceof File)) {
return badRequest("missing required file field");
}
const language = form.get("language")?.toString() || "auto";
const prompt = form.get("prompt")?.toString() || "";
const modelRaw = form.get("model")?.toString() || "";
const enableITN = (() => {
const m = modelRaw.trim().toLowerCase();
return m === ":itn" || m.endsWith(":itn");
})();
// 路由判定:存在 Bearer token 则走 DashScope,否则走 Z.ai
const auth = request.headers.get("Authorization");
const dashKey = auth && auth.startsWith("Bearer ") ? auth.slice(7).trim() : "";
if (dashKey) {
return await handleDashscope({ file, language, prompt, modelRaw, enableITN, dashKey });
}
// Read file and encode to base64
let b64 = "";
let sizeBytes = 0;
try {
const ab = await file.arrayBuffer();
sizeBytes = ab.byteLength;
b64 = await encodeBase64(ab);
} catch (e) {
return json({ error: `failed to read file: ${String(e?.message || e)}` }, 500);
}
const upstream = (env && env.UPSTREAM_ASR_ENDPOINT) || DEFAULT_UPSTREAM_ASR_ENDPOINT;
const payload = {
audio_file: {
data: b64,
name: file.name || "upload",
type: (file.type && file.type !== "application/octet-stream") ? file.type : mimeFromName(file.name || ""),
size: (typeof file.size === "number" && file.size >= 0) ? file.size : sizeBytes,
},
language,
};
const context = prompt.trim();
if (context) payload.context = context;
if (enableITN) payload.enable_itn = true;
let upResp;
try {
upResp = await fetch(upstream, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
} catch (e) {
return json({ error: `upstream request failed: ${String(e?.message || e)}` }, 502);
}
// Parse upstream response efficiently
const ct = upResp.headers.get("content-type") || "";
let upJSON;
if (ct.includes("application/json")) {
try {
upJSON = await upResp.json();
} catch (e) {
// Fallback to text for error details
const fallbackText = await upResp.text();
return json({ error: "invalid upstream json", detail: fallbackText }, 502);
}
if (!upResp.ok) {
return json({ error: "upstream not ok", detail: upJSON }, 502);
}
} else {
const upText = await upResp.text();
if (!upResp.ok) {
return json({ error: "upstream not ok", detail: upText }, 502);
}
try {
upJSON = JSON.parse(upText);
} catch {
return json({ error: "invalid upstream response", detail: upText }, 502);
}
}
if (upJSON && upJSON.success === false) {
return json(upJSON, 502);
}
const data = Array.isArray(upJSON?.data) ? upJSON.data : [];
const recognizedText = data[0] || "";
return json({ text: recognizedText }, 200);
}
return json({ error: "not found" }, 404);
},
};
网友解答:
--【壹】--:
不太想回答这个问题,贫道只能告诉你咸鱼搜索阿里云 300
祝你用的开心
--【贰】--:
强呀,大佬
--【叁】--:
牛哇!!!感谢分享!!
--【肆】--:
如果你到CF的网络存在性能问题的话,我的这个改进方法并不会让你用起来更顺畅
我建议你可以用香港/新加坡的节点并且确保你能到节点的网络是《24小时的顺畅》这样用起来才能得到很好的解决
--【伍】--:
弱弱问下,有试过的大佬效果改善明显吗?目前我是在cf上搭建的转换服务,实际使用上,手机端的延迟几乎没法用,macOS端在本地搭建的转换服务,倒是很快。感觉更多的是cf的往返节点的延迟更大吧?
--【陆】--:
谢谢,我用量不多,看了这个三百有效期一年,给我用浪费了,还是按量付费就行。
--【柒】--:
非常感谢,测试目前用上了新加坡,速度快多了。就是没赠送时长,请问有优惠的充值方式吗?闲鱼上没看到优惠券之类的。
--【捌】--:
并不强,做出脚本的大佬比较强 我只是对脚本中可能导致性能问题的点提出修正

