Add audio

This commit is contained in:
2025-12-10 09:38:38 +01:00
parent 65b8baabdc
commit b178c68434
9 changed files with 614 additions and 13 deletions

View File

@@ -61,6 +61,11 @@ public class SchemaMigration implements ApplicationRunner {
addColumnIfMissing("assets", columns, "speed", "REAL", "1.0");
addColumnIfMissing("assets", columns, "muted", "BOOLEAN", "0");
addColumnIfMissing("assets", columns, "media_type", "TEXT", "'application/octet-stream'");
addColumnIfMissing("assets", columns, "audio_loop", "BOOLEAN", "0");
addColumnIfMissing("assets", columns, "audio_delay_millis", "INTEGER", "0");
addColumnIfMissing("assets", columns, "audio_speed", "REAL", "1.0");
addColumnIfMissing("assets", columns, "audio_pitch", "REAL", "1.0");
addColumnIfMissing("assets", columns, "audio_volume", "REAL", "1.0");
}
private void addColumnIfMissing(String tableName, List<String> existingColumns, String columnName, String dataType, String defaultValue) {

View File

@@ -35,6 +35,11 @@ public class Asset {
private String mediaType;
private String originalMediaType;
private Integer zIndex;
private Boolean audioLoop;
private Integer audioDelayMillis;
private Double audioSpeed;
private Double audioPitch;
private Double audioVolume;
private boolean hidden;
private Instant createdAt;
@@ -80,6 +85,21 @@ public class Asset {
if (this.zIndex == null || this.zIndex < 1) {
this.zIndex = 1;
}
if (this.audioLoop == null) {
this.audioLoop = Boolean.FALSE;
}
if (this.audioDelayMillis == null) {
this.audioDelayMillis = 0;
}
if (this.audioSpeed == null) {
this.audioSpeed = 1.0;
}
if (this.audioPitch == null) {
this.audioPitch = 1.0;
}
if (this.audioVolume == null) {
this.audioVolume = 1.0;
}
}
public String getId() {
@@ -210,6 +230,46 @@ public class Asset {
this.zIndex = zIndex == null ? null : Math.max(1, zIndex);
}
public boolean isAudioLoop() {
return audioLoop != null && audioLoop;
}
public void setAudioLoop(Boolean audioLoop) {
this.audioLoop = audioLoop;
}
public Integer getAudioDelayMillis() {
return audioDelayMillis == null ? 0 : Math.max(0, audioDelayMillis);
}
public void setAudioDelayMillis(Integer audioDelayMillis) {
this.audioDelayMillis = audioDelayMillis;
}
public double getAudioSpeed() {
return audioSpeed == null ? 1.0 : Math.max(0.1, audioSpeed);
}
public void setAudioSpeed(Double audioSpeed) {
this.audioSpeed = audioSpeed;
}
public double getAudioPitch() {
return audioPitch == null ? 1.0 : Math.max(0.5, audioPitch);
}
public void setAudioPitch(Double audioPitch) {
this.audioPitch = audioPitch;
}
public double getAudioVolume() {
return audioVolume == null ? 1.0 : Math.max(0.0, Math.min(1.0, audioVolume));
}
public void setAudioVolume(Double audioVolume) {
this.audioVolume = audioVolume;
}
private static String normalize(String value) {
return value == null ? null : value.toLowerCase(Locale.ROOT);
}

View File

@@ -17,6 +17,11 @@ public record AssetView(
String mediaType,
String originalMediaType,
Integer zIndex,
Boolean audioLoop,
Integer audioDelayMillis,
Double audioSpeed,
Double audioPitch,
Double audioVolume,
boolean hidden,
Instant createdAt
) {
@@ -36,6 +41,11 @@ public record AssetView(
asset.getMediaType(),
asset.getOriginalMediaType(),
asset.getZIndex(),
asset.isAudioLoop(),
asset.getAudioDelayMillis(),
asset.getAudioSpeed(),
asset.getAudioPitch(),
asset.getAudioVolume(),
asset.isHidden(),
asset.getCreatedAt()
);

View File

@@ -9,6 +9,11 @@ public class TransformRequest {
private Double speed;
private Boolean muted;
private Integer zIndex;
private Boolean audioLoop;
private Integer audioDelayMillis;
private Double audioSpeed;
private Double audioPitch;
private Double audioVolume;
public double getX() {
return x;
@@ -73,4 +78,44 @@ public class TransformRequest {
public void setZIndex(Integer zIndex) {
this.zIndex = zIndex;
}
public Boolean getAudioLoop() {
return audioLoop;
}
public void setAudioLoop(Boolean audioLoop) {
this.audioLoop = audioLoop;
}
public Integer getAudioDelayMillis() {
return audioDelayMillis;
}
public void setAudioDelayMillis(Integer audioDelayMillis) {
this.audioDelayMillis = audioDelayMillis;
}
public Double getAudioSpeed() {
return audioSpeed;
}
public void setAudioSpeed(Double audioSpeed) {
this.audioSpeed = audioSpeed;
}
public Double getAudioPitch() {
return audioPitch;
}
public void setAudioPitch(Double audioPitch) {
this.audioPitch = audioPitch;
}
public Double getAudioVolume() {
return audioVolume;
}
public void setAudioVolume(Double audioVolume) {
this.audioVolume = audioVolume;
}
}

View File

@@ -125,13 +125,18 @@ public class ChannelDirectoryService {
.orElse("Asset " + System.currentTimeMillis());
String dataUrl = "data:" + optimized.mediaType() + ";base64," + Base64.getEncoder().encodeToString(optimized.bytes());
double width = optimized.width() > 0 ? optimized.width() : 640;
double height = optimized.height() > 0 ? optimized.height() : 360;
double width = optimized.width() > 0 ? optimized.width() : (optimized.mediaType().startsWith("audio/") ? 400 : 640);
double height = optimized.height() > 0 ? optimized.height() : (optimized.mediaType().startsWith("audio/") ? 80 : 360);
Asset asset = new Asset(channel.getBroadcaster(), name, dataUrl, width, height);
asset.setOriginalMediaType(mediaType);
asset.setMediaType(optimized.mediaType());
asset.setSpeed(1.0);
asset.setMuted(optimized.mediaType().startsWith("video/"));
asset.setAudioLoop(false);
asset.setAudioDelayMillis(0);
asset.setAudioSpeed(1.0);
asset.setAudioPitch(1.0);
asset.setAudioVolume(1.0);
asset.setZIndex(nextZIndex(channel.getBroadcaster()));
assetRepository.save(asset);
@@ -159,6 +164,22 @@ public class ChannelDirectoryService {
if (request.getMuted() != null && asset.isVideo()) {
asset.setMuted(request.getMuted());
}
if (request.getAudioLoop() != null) {
asset.setAudioLoop(request.getAudioLoop());
}
if (request.getAudioDelayMillis() != null && request.getAudioDelayMillis() >= 0) {
asset.setAudioDelayMillis(request.getAudioDelayMillis());
}
if (request.getAudioSpeed() != null && request.getAudioSpeed() >= 0) {
asset.setAudioSpeed(request.getAudioSpeed());
}
if (request.getAudioPitch() != null && request.getAudioPitch() > 0) {
asset.setAudioPitch(request.getAudioPitch());
}
if (request.getAudioVolume() != null && request.getAudioVolume() >= 0) {
double clamped = Math.max(0.0, Math.min(1.0, request.getAudioVolume()));
asset.setAudioVolume(clamped);
}
assetRepository.save(asset);
AssetView view = AssetView.from(normalized, asset);
messagingTemplate.convertAndSend(topicFor(broadcaster), AssetEvent.updated(broadcaster, view));
@@ -289,6 +310,9 @@ public class ChannelDirectoryService {
case "mp4" -> "video/mp4";
case "webm" -> "video/webm";
case "mov" -> "video/quicktime";
case "mp3" -> "audio/mpeg";
case "wav" -> "audio/wav";
case "ogg" -> "audio/ogg";
default -> "application/octet-stream";
})
.orElse("application/octet-stream");
@@ -324,6 +348,10 @@ public class ChannelDirectoryService {
return new OptimizedAsset(bytes, mediaType, dimensions.width(), dimensions.height());
}
if (mediaType.startsWith("audio/")) {
return new OptimizedAsset(bytes, mediaType, 0, 0);
}
BufferedImage image = ImageIO.read(new ByteArrayInputStream(bytes));
if (image != null) {
return new OptimizedAsset(bytes, mediaType, image.getWidth(), image.getHeight());