Search Docs
Loading...
Skip to content

Audio

Add audio to video scenes, extract audio from video fills, control playback, and generate waveform data with CE.SDK for Android.

8 mins
estimated time
GitHub

Audio blocks let you add background music, voice-overs, sound effects, and other standalone audio to video scenes. CE.SDK also exposes video audio track counts, playback controls, trim controls, and waveform generation through the Engine block API.

Playback examples require an Engine instance created with AudioContext.AUTO, which is the default. Engines created with AudioContext.NONE can still edit and export scenes, but they do not play audio.

Use Cases#

Use CE.SDK audio APIs when you need to add or manage:

  • Background music
  • Voice-overs
  • Sound effects
  • Podcast or narration tracks

How Audio Works in CE.SDK#

CE.SDK represents standalone audio as DesignBlockType.Audio blocks. Audio blocks are attached to a page, reference their media through audio/fileURI, and use the same timeline properties as other time-based blocks.

Each audio block can have:

  • A source URI for standalone audio files
  • Playback properties such as volume, mute state, playback time, and speed
  • Timeline properties such as offset, duration, trim offset, and trim length
  • Waveform sample data for custom timeline UIs

Extraction APIs create separate audio blocks from video fill tracks instead of changing an existing audio block’s source.

What Are the Time-Based Properties#

Audio timing is expressed in seconds:

  • Offset: when the audio block starts relative to its parent.
  • Duration: how long the block stays active on the timeline.
  • Trim offset: where playback starts inside the source audio.
  • Trim length: how much of the source audio is used before it loops or stops. Use the looping APIs to choose that behavior.

What Are Waveforms#

Waveforms are sampled audio amplitudes that you can render in a custom UI. On Android, generateAudioThumbnailSequence() returns a Flow of AudioThumbnailResult chunks. Each chunk contains normalized samples in the range from 0 to 1. Stereo requests interleave left and right samples.

When to Create vs. Extract Audio#

Create an audio block when the source is an external audio file such as background music or a voice-over. Extract audio when the sound already exists inside a video fill and you need a separate audio block for editing, trimming, or muting the original video.

Examples#

The snippets below use a video scene with an existing page. Audio APIs run on the main thread through the same Engine instance as the rest of your scene edits.

Create Audio#

Create a standalone audio block, attach it to the page, set its source URI, and load the resource before reading media metadata.

val audioBlock = engine.block.create(DesignBlockType.Audio)
engine.block.appendChild(parent = page, child = audioBlock)
engine.block.setUri(
block = audioBlock,
property = "audio/fileURI",
value = Uri.parse("https://cdn.img.ly/assets/demo/v1/ly.img.audio/audios/far_from_home.m4a"),
)
engine.block.forceLoadAVResource(audioBlock)
val resourceDuration = engine.block.getAVResourceTotalDuration(audioBlock)

Extract or Count Video Audio#

Create a video fill and wait for the suspend forceLoadAVResource() API to complete before extracting or counting video audio. The tabs below use this loaded videoFill.

val videoBlock = engine.block.create(DesignBlockType.Graphic)
engine.block.setShape(videoBlock, shape = engine.block.createShape(ShapeType.Rect))
val videoFill = engine.block.createFill(FillType.Video)
engine.block.setUri(
block = videoFill,
property = "fill/video/fileURI",
value = Uri.parse("https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-kampus-production-8154913.mp4"),
)
engine.block.setFill(videoBlock, fill = videoFill)
engine.block.appendChild(parent = page, child = videoBlock)
engine.block.forceLoadAVResource(videoFill)

Check that the loaded source contains audio, and extract the first track into a new audio block. AudioFromVideoOptions keeps the trim settings and mutes the source video fill.

val trackCountBeforeExtraction = engine.block.getAudioTrackCountFromVideo(videoFill)
check(trackCountBeforeExtraction > 0) {
"Video source must contain an audio track."
}
val extractedAudioBlock = engine.block.createAudioFromVideo(
videoFill = videoFill,
trackIndex = 0,
options = AudioFromVideoOptions(
keepTrimSettings = true,
muteOriginalVideo = true,
),
)
engine.block.appendChild(parent = page, child = extractedAudioBlock)

Control Audio Playback#

Playback time and play or pause state are usually controlled on the page so all time-based blocks stay synchronized. Volume, mute state, and playback speed are set on the audio block itself.

engine.block.setPlaybackTime(page, time = 3.0)
val playbackTime = engine.block.getPlaybackTime(page)
engine.block.setPlaying(page, enabled = true)
val playing = engine.block.isPlaying(page)
engine.block.setPlaying(page, enabled = false)
val paused = !engine.block.isPlaying(page)
engine.block.setVolume(audioBlock, volume = 0.7F)
val volume = engine.block.getVolume(audioBlock)
engine.block.setMuted(audioBlock, muted = true)
val muted = engine.block.isMuted(audioBlock)
engine.block.setPlaybackSpeed(audioBlock, speed = 1.25F)
val playbackSpeed = engine.block.getPlaybackSpeed(audioBlock)
engine.block.setSoloPlaybackEnabled(audioBlock, enabled = true)
val soloPlaybackEnabled = engine.block.isSoloPlaybackEnabled(audioBlock)
engine.block.setSoloPlaybackEnabled(audioBlock, enabled = false)
val soloPlaybackDisabled = !engine.block.isSoloPlaybackEnabled(audioBlock)

Audio speed supports values from 0.25 to 3.0 for audio blocks. Changing speed also changes how long the block takes on the timeline.

Manage Audio Timing#

Use offset and duration to place the audio block on the scene timeline. Use trim offset and trim length to choose which part of the source file plays, and set looping when the trimmed source should repeat while the block remains active.

engine.block.setPlaybackSpeed(audioBlock, speed = 1.0F)
engine.block.setTimeOffset(audioBlock, offset = 2.0)
val timeOffset = engine.block.getTimeOffset(audioBlock)
engine.block.setDuration(audioBlock, duration = 8.0)
engine.block.setTrimOffset(audioBlock, offset = 1.0)
val trimOffset = engine.block.getTrimOffset(audioBlock)
engine.block.setLooping(audioBlock, looping = true)
val looping = engine.block.isLooping(audioBlock)
engine.block.setTrimLength(audioBlock, length = 6.0)
val trimLength = engine.block.getTrimLength(audioBlock)
val blockDuration = engine.block.getDuration(audioBlock)

Load the audio resource before trimming so CE.SDK can read the source duration and metadata.

Generate Audio Thumbnails#

Waveform generation emits a Flow of chunks. Choose samplesPerChunk, a time range, the total number of samples, and the number of channels you want to render.

val waveformChunks = engine.block.generateAudioThumbnailSequence(
block = audioBlock,
samplesPerChunk = 4,
timeBegin = 0.0,
timeEnd = 4.0,
numberOfSamples = 16,
numberOfChannels = 1,
).toList()
val waveformSampleCount = waveformChunks.sumOf { chunk -> chunk.samples.size }

Render the returned sample values in your own timeline or waveform component.

Save Audio Scenes#

The current Android binding does not expose an audio-only export method. Persist audio edits by saving the scene, or export a video page with audio through the video export APIs when you need an MP4 result.

Extracted audio can reference a transient buffer:// resource. Before calling saveToString(), read each transient resource, store it with your app’s storage client, then call relocateResource() so the scene contains durable URIs only. The default allowedResourceSchemes list is blob, bundle, file, http, and https; buffer:// is always transient. The sample narrows the list to http and https, so saving fails if any extracted buffer:// resource or local blob, bundle, or file resource remains instead of being relocated.

val transientAudioResources = engine.editor.findAllTransientResources()
transientAudioResources.forEach { (transientUri, _) ->
val resourceBytes = ByteArrayOutputStream()
engine.editor.getResourceData(
uri = transientUri,
chunkSize = 64 * 1024,
) { chunk ->
val copy = chunk.duplicate()
val bytes = ByteArray(copy.remaining())
copy.get(bytes)
resourceBytes.write(bytes)
true
}
val permanentUri = uploadTransientAudioResource(
sourceUri = transientUri,
data = resourceBytes.toByteArray(),
)
engine.editor.relocateResource(
currentUri = transientUri,
relocatedUri = permanentUri,
)
}
val remainingTransientAudioResources = engine.editor.findAllTransientResources()
val savedScene = engine.scene.saveToString(
scene = scene,
allowedResourceSchemes = listOf("http", "https"),
)

The upload helper represents your app storage layer. Replace it with a real upload or local persistence implementation that returns a URI your app can load later.

private fun uploadTransientAudioResource(
sourceUri: Uri,
data: ByteArray,
): Uri {
check(data.isNotEmpty()) { "Cannot persist an empty audio resource." }
// Replace this with your app's storage client and return its permanent URI.
// Transient buffer URIs do not carry stable file names, so the app owns the storage key.
val sourceId = sourceUri.toString().hashCode().toString(radix = 16)
val fileName = "extracted-audio-$sourceId-${data.size}.m4a"
return Uri.parse("https://your-storage.example/audio/$fileName")
}

API Reference#

The table below lists the Android APIs used in the examples above.

CategoryAPIPurpose
Engine audio contextEngine.getInstance(id=_, audioContext=AudioContext.AUTO)Create an Engine instance that can play audio
Create blocksengine.block.create(blockType=DesignBlockType.Audio)Create a standalone audio block
Create blocksengine.block.create(blockType=DesignBlockType.Graphic)Create a block that can display the video fill
Create blocksengine.block.createShape(type=_)Create the video block shape
Create blocksengine.block.createFill(fillType=_)Create a video fill for extraction
Scene hierarchyengine.block.appendChild(parent=_, child=_)Attach audio, video, or extracted blocks to the scene hierarchy
Assign sourcesengine.block.setUri(block=_, property="audio/fileURI", value=_)Attach an audio file URI to an audio block
Assign sourcesengine.block.setUri(block=_, property="fill/video/fileURI", value=_)Attach a video file URI to a video fill
Video fill setupengine.block.setShape(block=_, shape=_)Assign a shape to the video block
Video fill setupengine.block.setFill(block=_, fill=_)Assign the loaded video fill to the video block
Extract video audioengine.block.createAudioFromVideo(videoFill=_, trackIndex=_, options=_)Extract one audio track by zero-based audio-track ordinal from a video fill
Extract video audioengine.block.createAudiosFromVideo(videoFill=_, options=_)Extract every audio track from a video fill
Count video audioengine.block.getAudioTrackCountFromVideo(videoFill=_)Count audio tracks in a video fill
Inspect video audioengine.block.getAudioInfoFromVideo(videoFill=_)Read AudioTrackInfo metadata; use the returned list position for extraction because AudioTrackInfo.trackIndex is the container track index
Playbackengine.block.setPlaying(block=_, enabled=_)Start or stop playback for a page or playable block
Playbackengine.block.isPlaying(block=_)Read the current play or pause state
Playbackengine.block.supportsPlaybackControl(block=_)Check whether playback control APIs are supported for a block
Playbackengine.block.setPlaybackTime(block=_, time=_)Move playback to a timeline position
Playbackengine.block.getPlaybackTime(block=_)Read the current playback time
Playbackengine.block.supportsPlaybackTime(block=_)Check whether a block exposes a playback time cursor
Playbackengine.block.setVolume(block=_, volume=_)Set volume from 0.0 to 1.0
Playbackengine.block.getVolume(block=_)Read the current volume
Playbackengine.block.setMuted(block=_, muted=_)Mute or unmute audio
Playbackengine.block.isMuted(block=_)Read whether audio is muted
Playbackengine.block.setPlaybackSpeed(block=_, speed=_)Set audio speed from 0.25x to 3.0x
Playbackengine.block.getPlaybackSpeed(block=_)Read the current playback speed
Playbackengine.block.setSoloPlaybackEnabled(block=_, enabled=_)Preview one block while the rest of the scene stays paused
Playbackengine.block.isSoloPlaybackEnabled(block=_)Read whether solo playback is enabled for a block
Timingengine.block.supportsTimeOffset(block=_)Check whether a block can be positioned on its parent’s timeline
Timingengine.block.setTimeOffset(block=_, offset=_)Move the audio block on the timeline
Timingengine.block.getTimeOffset(block=_)Read where the audio block starts on the timeline
Timingengine.block.supportsDuration(block=_)Check whether a block exposes an active timeline duration
Timingengine.block.setDuration(block=_, duration=_)Set the active block duration
Timingengine.block.getDuration(block=_)Read the active block duration
Timingengine.block.supportsTrim(block=_)Check whether a block or fill exposes trim controls
Timingengine.block.setTrimOffset(block=_, offset=_)Start inside the source audio
Timingengine.block.getTrimOffset(block=_)Read the source trim start
Timingengine.block.setTrimLength(block=_, length=_)Limit the source range used for playback
Timingengine.block.getTrimLength(block=_)Read the source trim length
Timingengine.block.setLooping(block=_, looping=_)Loop the trimmed source while the block is active
Timingengine.block.isLooping(block=_)Read whether the source loops or stops
Resourcesengine.block.forceLoadAVResource(block=_)Load audio or video metadata before querying it
Resourcesengine.block.getAVResourceTotalDuration(block=_)Read the loaded audio or video source duration
Waveformsengine.block.generateAudioThumbnailSequence(block=_, samplesPerChunk=_, timeBegin=_, timeEnd=_, numberOfSamples=_, numberOfChannels=_)Generate waveform sample chunks
Persistenceengine.editor.findAllTransientResources()Find extracted buffer:// resources that must be persisted before saving
Persistenceengine.editor.getResourceData(uri=_, chunkSize=_, onData=_)Read transient resource bytes for app storage
Persistenceengine.editor.relocateResource(currentUri=_, relocatedUri=_)Replace a transient URI with a durable URI
Persistenceengine.scene.saveToString(scene=_, allowedResourceSchemes=_)Serialize only scenes whose resources use allowed durable schemes

Next Steps#