Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/adapters/media-adapter.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Media Playback Context Adapter
* Normalizes raw playback histories into unified CCP music signals.
*/

/**
* Parses a raw media history log string or a telemetry track frame object.
* @param {Object|string} rawPlayback - Input media playback detail data
* @returns {Object} Standardized CCP context metadata payload
*/
export function parseMediaPlayback(rawPlayback) {
let trackName = "Unknown Track";
let artistName = "Unknown Artist";
let contextUri = "";
let durationMs = 0;
let timestamp = new Date().toISOString();

// Support parsing either a direct raw track title string or structured JSON telemetry
if (typeof rawPlayback === "string") {
const parts = rawPlayback.split(" - ");
if (parts.length >= 2) {
artistName = parts[0].trim();
trackName = parts[1].trim();
} else {
trackName = rawPlayback.trim();
}
} else if (rawPlayback && typeof rawPlayback === "object") {
// Extract property variables matching standard media streaming webhook structures (e.g. Spotify)
trackName = rawPlayback.track_name || rawPlayback.track?.name || trackName;
artistName = rawPlayback.artist_name || rawPlayback.track?.artists?.[0]?.name || artistName;
contextUri = rawPlayback.context_uri || rawPlayback.context?.uri || "";
durationMs = Number(rawPlayback.duration_ms || rawPlayback.track?.duration_ms || 0);

if (rawPlayback.played_at || rawPlayback.timestamp) {
timestamp = new Date(rawPlayback.played_at || rawPlayback.timestamp).toISOString();
}
}

return {
schema_version: "memact.ccp_observation.v1",
event_type: "media_playback",
category: "music",
title: `Listened to ${trackName} by ${artistName}`,
evidence: {
track_title: trackName,
artist: artistName,
duration_ms: durationMs,
streaming_context_uri: contextUri
},
occurred_at: timestamp
};
}
3 changes: 2 additions & 1 deletion src/index.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { createMemactClient, MemactClient } from "./client.mjs"
export { MemactSDKError } from "./errors.mjs"
export { validateClientConfig } from "./config-validation.mjs"
export { MemactProvider, useContextClaim } from "./react.mjs"
export { parseMediaPlayback } from "./adapters/media-adapter.mjs"
export { MemactProvider, useContextClaim } from "./react.mjs"
40 changes: 40 additions & 0 deletions test/media-adapter.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import test from "node:test"
import assert from "node:assert/strict"
import { parseMediaPlayback } from "../src/adapters/media-adapter.mjs"

test("parseMediaPlayback splits a raw text log string correctly into artist and track", () => {
const rawLog = "Daft Punk - One More Time";
const result = parseMediaPlayback(rawLog);

assert.equal(result.schema_version, "memact.ccp_observation.v1");
assert.equal(result.category, "music");
assert.equal(result.evidence.artist, "Daft Punk");
assert.equal(result.evidence.track_title, "One More Time");
});

test("parseMediaPlayback falls back cleanly if string format lacks standard delimiter", () => {
const rawLog = "Ambient White Noise Soundscape Loops";
const result = parseMediaPlayback(rawLog);

assert.equal(result.evidence.artist, "Unknown Artist");
assert.equal(result.evidence.track_title, "Ambient White Noise Soundscape Loops");
});

test("parseMediaPlayback normalizes structured Spotify webhook payload data structures", () => {
const payloadMock = {
played_at: "2026-07-12T07:30:00Z",
context_uri: "spotify:playlist:37i9dQZF1DX4sWSpwq3LiO",
track: {
name: "Starboy",
duration_ms: 230453,
artists: [{ name: "The Weeknd" }]
}
};

const result = parseMediaPlayback(payloadMock);
assert.equal(result.evidence.track_title, "Starboy");
assert.equal(result.evidence.artist, "The Weeknd");
assert.equal(result.evidence.duration_ms, 230453);
assert.equal(result.evidence.streaming_context_uri, "spotify:playlist:37i9dQZF1DX4sWSpwq3LiO");
assert.equal(result.occurred_at, "2026-07-12T07:30:00.000Z");
});