Danmuku
Display timed comments over the video, with an input panel, display settings and an optional heatmap. This guide describes the current refactor branch. The /runtime entrypoint and refactor fixes have not yet been published to npm; unversioned CDN links still load the published release.
Demo
Open the full example. Run Code examples below use the site's local plugin build and sample media. In your application, use your own container and media URLs.
Installation
npm install artplayer artplayer-plugin-danmukuyarn add artplayer artplayer-plugin-danmukupnpm add artplayer artplayer-plugin-danmuku<script src="path/to/artplayer.js"></script>
<script src="path/to/artplayer-plugin-danmuku.js"></script>JavaScript projects can import the default factory from artplayer-plugin-danmuku. Script builds expose artplayerPluginDanmuku; pass its result to the player's plugins array.
CDN
https://cdn.jsdelivr.net/npm/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.jshttps://unpkg.com/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.jsComment structure
Only text is required. Empty or whitespace-only comments are ignored. Direct emit() and loaded rows retain the original text, including surrounding spaces; only the input panel trims its submitted text.
({
text: 'Hello!',
time: 10, // Seconds; omitted time defaults to currentTime + 0.5
mode: 0, // 0: scrolling, 1: top, 2: bottom; defaults to option.mode
color: '#FFFFFF', // Defaults to option.color
border: false,
style: {}, // CSS properties for this comment
});Explicit time: 0 is preserved. Negative times are clamped to zero. Other than 0, 1 and 2, comment modes are ignored.
Normalization fills time, mode, color and style on the supplied object before calling filter. The queue then receives a shallow copy; its style object is still shared. Copy reusable inputs when these mutations matter. The optional string id becomes the displayed element's data-id; it does not deduplicate comments. Text is rendered through textContent, not as HTML.
All options
Pass an option object to the factory. At runtime, {} is valid and all fields have defaults. The historical root declarations still require danmuku; existing TypeScript projects can keep providing it. See TypeScript for accurate declarations.
({
danmuku: [], // Array, XML URL, Promise of an array, or function returning an array/Promise
speed: 5, // Display duration in seconds, clamped to 1–10
margin: [10, '25%'], // Top/bottom spacing: pixels or percentages
opacity: 1, // Clamped to 0–1
color: '#FFFFFF', // Default comment color
mode: 0, // Default comment mode
modes: [0, 1, 2], // Visible modes
fontSize: 25, // Pixels or a percentage of player height
antiOverlap: true,
synchronousPlayback: false, // Follow video playbackRate when enabled
mount: undefined, // Defaults to the center of the player controls
heatmap: false, // Enable at construction: true or a heatmap options object
width: 512, // Below this width, the default input panel moves below the player
points: [], // Stored option; use the points event to draw custom data
filter: () => true, // Synchronous; do not return a Promise
beforeEmit: () => true, // Input-panel submissions only; may return a Promise
beforeVisible: () => true, // Called before display; may return a Promise
visible: true,
emitter: true, // Show the input panel's sending UI
maxLength: 200, // Input length, clamped to 1–1000
lockTime: 5, // Seconds between input-panel submissions, clamped to 1–60
theme: 'dark', // 'dark' or 'light' for an external mount
OPACITY: {},
FONT_SIZE: {},
MARGIN: {},
SPEED: {},
COLOR: [],
});OPACITY, FONT_SIZE, MARGIN and SPEED override slider definitions with min, max and steps. Each step can contain name, value, hide and show. hide suppresses its label; the retained show field is not read by the renderer. Margin values are pairs such as [10, '50%']. COLOR replaces the palette with an array of CSS color strings; an empty array uses the built-in palette.
Array, XML and asynchronous input
XML input uses Bilibili's comment format. Fetching a cross-origin XML URL requires that server to allow browser access. An input function runs without the option object as its receiver. It may return an array directly or asynchronously.
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [
artplayerPluginDanmuku({
danmuku: [{ text: 'Hello from an array', time: 1 }],
// Alternatives:
// danmuku: '/assets/sample/danmuku.xml',
// danmuku: Promise.resolve([{ text: 'From a Promise', time: 1 }]),
// danmuku: async () => [{ text: 'From a function', time: 1 }],
}),
],
});Lifecycle callbacks
Input-panel submissions run beforeEmit → filter → beforeVisible → artplayerPluginDanmuku:visible. Loaded comments and direct emit calls run filter → beforeVisible → artplayerPluginDanmuku:visible. This describes accepted comments; callbacks can reject them, and scheduling still requires playback and an available track.
| Callback | Input | Acceptance |
|---|---|---|
beforeEmit | Input-panel comment | Only strict true, or a Promise resolving to true, sends it |
filter | Comment with time, mode, color and style filled in | A synchronous truthy result adds it to the queue |
beforeVisible | Queue item | A truthy result, including an awaited result, permits display |
Normal functions receive the current option object as this for all three callbacks. Arrow functions retain their lexical this. emit() does not call beforeEmit: perform application validation before calling it when needed. beforeEmit failures are logged in the console and leave the input available for another attempt.
An asynchronous beforeVisible rejection emits artplayerPluginDanmuku:error once for that item in the current run; other items continue. Pause/resume, reset or replacing the callback permits a retry if the item is still eligible by time. Pausing, seeking, hiding, resetting or destroying cancels unfinished visibility preparation.
Methods and state
The registered plugin is available synchronously as art.plugins.artplayerPluginDanmuku.
| Member | Behavior and return value |
|---|---|
emit(comment) | Processes one comment for the queue; returns a Promise |
load() | Reads option.danmuku and replaces the queue; returns a Promise |
load(input) | Appends comments from the input; returns a Promise |
config(partialOption) | Synchronously merges configuration |
hide() / show() | Synchronously hides or shows the comment layer |
reset() | Clears displayed comments and returns queue items to waiting; does not delete the queue |
mount(target) | Moves the panel to an existing element or selector; returns undefined |
option | Live current configuration; use config() for validated updates |
isHide | Read-only live visibility state: true when hidden |
isStop | Read-only live stopped state; distinct from visibility and not a media-readiness signal |
emit/load Promises resolve to the internal Danmuku owner. config/hide/show/reset return that same owner synchronously. The owner is different from the registered plugin facade. Keep using the registered facade for subsequent commands. Awaiting emit() means queue processing has finished, not that the comment has appeared.
Loading and configuration
Changing config({ danmuku: input }) does not load the new input. Follow it with load() to replace the queue. Input-read failures leave the existing queue intact; failures while filtering individual rows do not guarantee an atomic rollback of the entire batch. Independent append operations do not cancel one another. A newer replacement cancels an unfinished older replacement. Destroy cancels pending loads. Cancelled Promises resolve to the owner without a late loaded or error event.
Fetch and response-text failures emit artplayerPluginDanmuku:error and reject the corresponding public load() Promise. Handle rejection with await/try...catch or .catch(...). Initial automatic loading observes rejection and logs a warning. Invalid configuration leaves the current option intact. Use mount(target) to move the panel; changing option.mount through config() does not perform a mount. Enable heatmap when constructing the plugin; config({ heatmap: true }) does not create it later.
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [artplayerPluginDanmuku({ danmuku: [], emitter: false })],
});
async function updateComments() {
var plugin = art.plugins.artplayerPluginDanmuku;
plugin.config({ danmuku: [{ text: 'Replacement', time: 1 }] });
await plugin.load();
await plugin.load([{ text: 'Appended', time: 2 }]);
await plugin.emit({ text: 'Scheduled from the current time' });
plugin.hide();
console.info('Hidden:', plugin.isHide);
plugin.show();
plugin.reset();
}
updateComments().catch(console.error);External mount
Create a separate mount element before constructing the plugin. The panel moves into the controls during player fullscreen or web fullscreen and returns to its configured mount on exit. Use theme: 'light' on a light background. The live mount(target) method requires a valid target; omitting its argument does not select the default. Destroy releases the plugin panel; the application owns any container it created.
var $danmu = document.createElement('div');
document.querySelector('.artplayer-app').after($danmu);
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreenWeb: true,
plugins: [artplayerPluginDanmuku({
danmuku: [{ text: 'External input panel', time: 1 }],
mount: $danmu,
theme: 'dark',
})],
});
art.on('destroy', () => $danmu.remove());
// Move it later with art.plugins.artplayerPluginDanmuku.mount(otherElement).Heatmap
Set heatmap: true at construction to sample the queue automatically. A live stream does not draw a heatmap. Dense automatic curves now fit in the bottom quarter of the chart instead of covering the video (issue #958). Explicit finite yMin or yMax and custom points retain their coordinate mapping.
An object can set xMin, xMax, yMin, yMax, scale, opacity, minHeight, sampling, smoothing and flattening. Defaults are xMin: 0, xMax: chartWidth, yMin: 0, yMax: 128, scale: 0.25, opacity: 0.2, minHeight: floor(chartHeight * 0.05), sampling: max(1, floor(chartWidth / 100)), smoothing: 0.2, flattening: 0.2.
Send art.emit('artplayerPluginDanmuku:points', points) to draw custom [x, value] pairs. The default x-axis uses chart pixels, not seconds. Set xMin/xMax explicitly if supplying another coordinate range. Rendering mutates the inner point arrays for historical compatibility: copy each pair if reusing the original data. The stored points option does not draw custom data. A resize or successful load redraws the automatic curve, so resend custom data after those events when needed.
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [artplayerPluginDanmuku({
danmuku: [{ text: 'Heatmap example', time: 1 }],
heatmap: true,
})],
});
var points = [[0, 5], [0.25, 12], [0.5, 30], [0.75, 10], [1, 5]];
function drawPoints() {
var width = art.controls.heatmap.offsetWidth;
art.emit('artplayerPluginDanmuku:points', points.map(([ratio, value]) => [ratio * width, value]));
}
art.on('ready', drawPoints);
art.on('resize', drawPoints);
art.on('artplayerPluginDanmuku:loaded', drawPoints);Events
Subscribe with art.on(name, callback) and remove subscriptions with art.off(name, callback).
| Event | Payload / meaning |
|---|---|
artplayerPluginDanmuku:visible | Queue item; its $ref is the displayed element |
artplayerPluginDanmuku:loaded | Current full queue after a successful load, including appends |
artplayerPluginDanmuku:error | Original error from loading or scheduling; handle public Promise rejections separately |
artplayerPluginDanmuku:config | Current configuration |
artplayerPluginDanmuku:start | No payload; scheduling starts |
artplayerPluginDanmuku:stop | No payload; scheduling stops |
artplayerPluginDanmuku:hide | No payload; layer hidden |
artplayerPluginDanmuku:show | No payload; layer shown |
artplayerPluginDanmuku:reset | No payload; displayed items reset |
artplayerPluginDanmuku:destroy | No payload; plugin destroyed |
artplayerPluginDanmuku:points | Application-sent custom points for the heatmap |
Events are not replayed to later listeners. In particular, an initially empty array can finish loading during construction. Subscribe before invoking a later load() if you need to observe its completion event. Use $ref.textContent when adding text in a visible handler.
TypeScript
The /runtime Owner type describes the internal return object for existing advanced integrations. Its art, option, queue and states are live objects; readys is the current candidate list. speed, fontSize, marginTop/marginBottom and isRotate are computed views. Do not mutate queue state to simulate public commands.
Owner's additional start/stop methods control comment scheduling and emit their events; continue/suspend resume or pause existing displayed items; update schedules work. These methods synchronously return Owner and do not control video playback. resize adjusts displayed items, while seek cancels old display preparation and schedules again; both return undefined. Owner's destroy cleans scheduling, nodes and input tasks and emits its destroy event, but does not fully uninstall the settings panel and heatmap. Use art.destroy() for complete cleanup. These additional commands are absent from the registered result.
The root and /legacy entrypoints keep the npm 5.3.0 declaration shapes for compatibility, including historical inaccuracies about return values. The current branch adds /runtime for accurate types while loading the same runtime factory:
import Artplayer from 'artplayer';
import danmuku from 'artplayer-plugin-danmuku/runtime';
import type { RuntimeOption, Point, EventMap } from 'artplayer-plugin-danmuku/runtime';
const option: RuntimeOption = { danmuku: [], heatmap: true };
const points: Point[] = [[0, 5], [100, 10]];
const onError = (...[error]: EventMap['artplayerPluginDanmuku:error']) => console.error(error);
const art = new Artplayer({ container: '#player', url: '/video.mp4', plugins: [danmuku(option)] });
art.on('artplayerPluginDanmuku:error', onError);The explicit EventMap describes payloads; it does not augment the core's historical event declarations automatically. The factory also exposes the existing icons object for customization.
Root named types are Mode, Danmuku, Slider, Danmu, Option and Result. /runtime exposes Mode, State, Margin, Point, Danmu, NormalizedDanmu, Item, Input, SliderStep, Slider, Heatmap, NormalizedOption, RuntimeOption, Owner, RuntimeResult, Icons, RuntimeFactory and EventMap. Item adds $state/$index/$ref/$restTime/$lastStartTime; nodes are reused and $ref becomes null when recycled. A node received by a visible handler does not permanently belong to that comment.
icons contains $on/$off/$config/$style, the three pairs $mode_0_off/$mode_0_on through $mode_2_off/$mode_2_on, and $check_on/$check_off. Applications can reuse these SVG strings in their own interfaces. The built-in panel reads bundled icons directly; changing the factory icons object does not replace its icons.
Package README.md and ARCHITECTURE.md describe module ownership, maintenance commands and remaining device/combination validation.
