Populating metadata
This commit is contained in:
@@ -6,6 +6,7 @@ const SPLICE_DOMAIN_RE = /^https?:\/\/([a-z0-9-]+\.)*splice\.com(\/|$)/i;
|
||||
const GRAPHQL_URL_FRAGMENT = "surfaces-graphql.splice.com/graphql";
|
||||
const PROBE_EVENT_SOURCE = "S3_CAPTURE_PROBE";
|
||||
const DEBUG_CAPTURE = true;
|
||||
const DEBUG_PLAYBACK_META_MARKER = "S3DBG_PLAYBAR_META_V2";
|
||||
let knownUrls = new Set();
|
||||
let knownPerformanceUrls = new Set();
|
||||
let observer = null;
|
||||
@@ -14,6 +15,13 @@ let performanceObserver = null;
|
||||
let fetchPatched = false;
|
||||
let xhrPatched = false;
|
||||
let probeListenerInstalled = false;
|
||||
let currentPlaybackFilename = "";
|
||||
let currentPlaybackKey = "";
|
||||
let currentPlaybackBpm = "";
|
||||
let currentPlaybackTags = [];
|
||||
let currentPlaybackCoverImageUrl = "";
|
||||
let lastPlaybackMetaSignature = "";
|
||||
let lastPlaybackMetaApplied = false;
|
||||
|
||||
function isDomainAllowed(url) {
|
||||
return typeof url === "string" && SPLICE_DOMAIN_RE.test(url);
|
||||
@@ -55,6 +63,121 @@ function normalizeUrl(rawUrl) {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeFileName(raw) {
|
||||
if (typeof raw !== "string") {
|
||||
return "";
|
||||
}
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
const parts = trimmed.split("/");
|
||||
return parts[parts.length - 1] || trimmed;
|
||||
}
|
||||
|
||||
function getPlaybackRoot() {
|
||||
const playbar = document.querySelector(TARGET_SELECTOR);
|
||||
if (playbar) {
|
||||
return playbar;
|
||||
}
|
||||
const filenameNode = document.querySelector('[data-qa="filename"]');
|
||||
if (filenameNode && typeof filenameNode.closest === "function") {
|
||||
return filenameNode.closest(".playbar-playback-info-bar");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function queryPlaybackNode(selector) {
|
||||
const root = getPlaybackRoot();
|
||||
if (!root) {
|
||||
return null;
|
||||
}
|
||||
return root.querySelector(selector);
|
||||
}
|
||||
|
||||
function getPlaybackFilenameFromDom() {
|
||||
const el = queryPlaybackNode('[data-qa="filename"]');
|
||||
if (!el) {
|
||||
return "";
|
||||
}
|
||||
return normalizeFileName(el.textContent || "");
|
||||
}
|
||||
|
||||
function getPlaybackKeyFromDom() {
|
||||
const el = queryPlaybackNode('[data-qa="key"]');
|
||||
return el ? String(el.textContent || "").trim() : "";
|
||||
}
|
||||
|
||||
function getPlaybackBpmFromDom() {
|
||||
const el = queryPlaybackNode('[data-qa="bpm"]');
|
||||
return el ? String(el.textContent || "").trim() : "";
|
||||
}
|
||||
|
||||
function getPlaybackTagsFromDom() {
|
||||
const root = queryPlaybackNode('[data-qa="tags"]');
|
||||
if (!root) {
|
||||
return [];
|
||||
}
|
||||
const tags = [];
|
||||
const spans = root.querySelectorAll("span.tag");
|
||||
for (const span of spans) {
|
||||
const value = String(span.textContent || "").trim();
|
||||
if (value) {
|
||||
tags.push(value);
|
||||
}
|
||||
}
|
||||
return Array.from(new Set(tags));
|
||||
}
|
||||
|
||||
function getPlaybackCoverImageFromDom() {
|
||||
const img = queryPlaybackNode('[data-qa="cover-art-image"]');
|
||||
if (!img) {
|
||||
return "";
|
||||
}
|
||||
const src = img.getAttribute("src") || img.currentSrc || "";
|
||||
return normalizeUrl(src);
|
||||
}
|
||||
|
||||
function extractCoverImageFromGraphqlItem(item) {
|
||||
if (!item || typeof item !== "object") {
|
||||
return "";
|
||||
}
|
||||
const ownFiles = Array.isArray(item.files) ? item.files : [];
|
||||
for (const file of ownFiles) {
|
||||
if (!file || typeof file !== "object") {
|
||||
continue;
|
||||
}
|
||||
const typeSlug = String(file.asset_file_type_slug || "").toLowerCase();
|
||||
if (typeSlug !== "cover_image") {
|
||||
continue;
|
||||
}
|
||||
const url = normalizeUrl(file.url || "");
|
||||
if (url) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
if (!item.parents || !Array.isArray(item.parents.items)) {
|
||||
return "";
|
||||
}
|
||||
for (const parent of item.parents.items) {
|
||||
const files = parent && Array.isArray(parent.files) ? parent.files : [];
|
||||
for (const file of files) {
|
||||
if (!file || typeof file !== "object") {
|
||||
continue;
|
||||
}
|
||||
const typeSlug = String(file.asset_file_type_slug || "").toLowerCase();
|
||||
if (typeSlug !== "cover_image") {
|
||||
continue;
|
||||
}
|
||||
const url = normalizeUrl(file.url || "");
|
||||
if (url) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function logDebug(message, extra) {
|
||||
if (!DEBUG_CAPTURE) {
|
||||
return;
|
||||
@@ -142,6 +265,13 @@ function extractPreviewContextsFromGraphql(json) {
|
||||
continue;
|
||||
}
|
||||
const sampleId = typeof item.uuid === "string" ? item.uuid.toLowerCase() : "";
|
||||
const itemName = normalizeFileName(String(item.name || ""));
|
||||
const itemKey = typeof item.key === "string" ? item.key.trim().toUpperCase() : "";
|
||||
const itemBpm = item.bpm == null ? "" : String(item.bpm).trim();
|
||||
const itemTags = Array.isArray(item.tags)
|
||||
? Array.from(new Set(item.tags.map((tag) => (tag && tag.label ? String(tag.label).trim() : "")).filter(Boolean)))
|
||||
: [];
|
||||
const itemCoverImageUrl = extractCoverImageFromGraphqlItem(item);
|
||||
const files = Array.isArray(item.files) ? item.files : [];
|
||||
|
||||
for (const file of files) {
|
||||
@@ -155,7 +285,12 @@ function extractPreviewContextsFromGraphql(json) {
|
||||
}
|
||||
contexts.push({
|
||||
sampleId: sampleId || extractSampleIdFromUrl(fileUrl),
|
||||
previewUrl: fileUrl
|
||||
previewUrl: fileUrl,
|
||||
fileName: itemName,
|
||||
key: itemKey,
|
||||
bpm: itemBpm,
|
||||
tags: itemTags,
|
||||
coverImageUrl: itemCoverImageUrl
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -221,7 +356,8 @@ function captureUrls(inputUrls) {
|
||||
browser.runtime.sendMessage({
|
||||
type: "CAPTURE_URLS",
|
||||
urls: newUrls,
|
||||
source: "content_capture"
|
||||
source: "content_capture",
|
||||
fileName: getPlaybackFilenameFromDom()
|
||||
}).catch(() => {
|
||||
// Ignore transient extension context failures.
|
||||
});
|
||||
@@ -499,6 +635,7 @@ function startUiObserver() {
|
||||
}
|
||||
observer = new MutationObserver(() => {
|
||||
injectControls();
|
||||
syncPlaybackMeta();
|
||||
});
|
||||
observer.observe(document.documentElement, { childList: true, subtree: true });
|
||||
}
|
||||
@@ -527,6 +664,71 @@ function startCaptureObserver() {
|
||||
});
|
||||
}
|
||||
|
||||
function syncPlaybackMeta() {
|
||||
const fileName = getPlaybackFilenameFromDom();
|
||||
const key = getPlaybackKeyFromDom();
|
||||
const bpm = getPlaybackBpmFromDom();
|
||||
const tags = getPlaybackTagsFromDom();
|
||||
const coverImageUrl = getPlaybackCoverImageFromDom();
|
||||
|
||||
const sameTags =
|
||||
tags.length === currentPlaybackTags.length &&
|
||||
tags.every((tag, idx) => tag === currentPlaybackTags[idx]);
|
||||
|
||||
if (
|
||||
(!fileName || fileName === currentPlaybackFilename) &&
|
||||
key === currentPlaybackKey &&
|
||||
bpm === currentPlaybackBpm &&
|
||||
sameTags &&
|
||||
coverImageUrl === currentPlaybackCoverImageUrl
|
||||
) {
|
||||
const signature = JSON.stringify({
|
||||
fileName: currentPlaybackFilename,
|
||||
key: currentPlaybackKey,
|
||||
bpm: currentPlaybackBpm,
|
||||
tags: currentPlaybackTags,
|
||||
coverImageUrl: currentPlaybackCoverImageUrl
|
||||
});
|
||||
if (lastPlaybackMetaApplied && signature === lastPlaybackMetaSignature) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (fileName) {
|
||||
currentPlaybackFilename = fileName;
|
||||
}
|
||||
currentPlaybackKey = key;
|
||||
currentPlaybackBpm = bpm;
|
||||
currentPlaybackTags = tags;
|
||||
currentPlaybackCoverImageUrl = coverImageUrl;
|
||||
const payload = {
|
||||
type: "UPDATE_ACTIVE_PLAYBACK_META",
|
||||
fileName: currentPlaybackFilename,
|
||||
key: currentPlaybackKey,
|
||||
bpm: currentPlaybackBpm,
|
||||
tags: currentPlaybackTags,
|
||||
coverImageUrl: currentPlaybackCoverImageUrl
|
||||
};
|
||||
if (DEBUG_CAPTURE) {
|
||||
console.debug(DEBUG_PLAYBACK_META_MARKER, {
|
||||
fileName: payload.fileName,
|
||||
key: payload.key,
|
||||
bpm: payload.bpm,
|
||||
tagsCount: Array.isArray(payload.tags) ? payload.tags.length : 0,
|
||||
coverImageUrl: payload.coverImageUrl
|
||||
});
|
||||
}
|
||||
const signature = JSON.stringify(payload);
|
||||
lastPlaybackMetaSignature = signature;
|
||||
lastPlaybackMetaApplied = false;
|
||||
browser.runtime.sendMessage(payload).then((result) => {
|
||||
// Keep retrying on the next interval until background confirms attachment.
|
||||
lastPlaybackMetaApplied = Boolean(result && result.ok);
|
||||
}).catch(() => {
|
||||
lastPlaybackMetaApplied = false;
|
||||
// Ignore transient extension messaging failures.
|
||||
});
|
||||
}
|
||||
|
||||
function installPageProbeListener() {
|
||||
if (probeListenerInstalled) {
|
||||
return;
|
||||
@@ -584,15 +786,21 @@ function init() {
|
||||
|
||||
installPageProbeListener();
|
||||
injectPageProbeScript();
|
||||
currentPlaybackFilename = getPlaybackFilenameFromDom();
|
||||
currentPlaybackKey = getPlaybackKeyFromDom();
|
||||
currentPlaybackBpm = getPlaybackBpmFromDom();
|
||||
currentPlaybackTags = getPlaybackTagsFromDom();
|
||||
currentPlaybackCoverImageUrl = getPlaybackCoverImageFromDom();
|
||||
lastPlaybackMetaSignature = "";
|
||||
lastPlaybackMetaApplied = false;
|
||||
scanNodeForUrls(document);
|
||||
patchFetch();
|
||||
patchXhr();
|
||||
capturePerformanceResourceUrls();
|
||||
startPerformanceObserver();
|
||||
window.setInterval(capturePerformanceResourceUrls, 2000);
|
||||
// Avoid buffered resource sweeps; they can re-capture stale previews.
|
||||
injectControls();
|
||||
startUiObserver();
|
||||
startCaptureObserver();
|
||||
window.setInterval(syncPlaybackMeta, 700);
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
Reference in New Issue
Block a user