Skip to content

Commit

Permalink
Audio markers and appenders (#44)
Browse files Browse the repository at this point in the history
* Make audio markers work

* Make tests for audio markers

* Updates
  • Loading branch information
wsargent committed May 18, 2019
1 parent 9f7aa14 commit a68959b
Show file tree
Hide file tree
Showing 23 changed files with 917 additions and 1 deletion.
1 change: 1 addition & 0 deletions logback-audio/gradle.properties
@@ -0,0 +1 @@
project_description = Logback Audio Markers
15 changes: 15 additions & 0 deletions logback-audio/logback-audio.gradle
@@ -0,0 +1,15 @@
dependencies {
compile project(':logback-core')
compile group: 'com.googlecode.soundlibs', name: 'mp3spi', version: '1.9.5.4'
compile group: 'com.github.trilarion', name: 'vorbis-support', version: '1.1.0'
}

config {
javadoc {
options.charSet = 'UTF-8'
options.encoding = 'UTF-8'
options.docEncoding = 'UTF-8'
options.use = true
options.links = [jvmToJavadoc(targetCompatibility)] + javadocFromDependencies(configurations.compile)
}
}
@@ -0,0 +1,44 @@
/*
* SPDX-License-Identifier: CC0-1.0
*
* Copyright 2018-2019 Will Sargent.
*
* Licensed under the CC0 Public Domain Dedication;
* You may obtain a copy of the License at
*
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package com.tersesystems.logback.audio;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;

public class AudioLevelAppender extends AppenderBase<ILoggingEvent> implements PlayerAttachable {

private Player player;

@Override
protected void append(ILoggingEvent eventObject) {
player.play();
}

@Override
public void addPlayer(Player player) {
addInfo("player = " + player);
this.player = player;
}

@Override
public void clearAllPlayers() {
this.player = null;
}

@Override
public void start() {
if (player == null) {
addError("No player found!");
} else {
super.start();
}
}
}
@@ -0,0 +1,50 @@
/*
* SPDX-License-Identifier: CC0-1.0
*
* Copyright 2018-2019 Will Sargent.
*
* Licensed under the CC0 Public Domain Dedication;
* You may obtain a copy of the License at
*
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package com.tersesystems.logback.audio;

import com.tersesystems.logback.TerseBasicMarker;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Path;

public class AudioMarker extends TerseBasicMarker implements Player {

private static final String MARKER_NAME = "TS_AUDIO_MARKER";

private final Player player;

public AudioMarker(URL url) {
super(MARKER_NAME);
player = SimplePlayer.fromURL(url);
}

public AudioMarker(Path path) {
super(MARKER_NAME);
player = SimplePlayer.fromPath(path);
}

public AudioMarker(InputStream inputStream, String name) {
super(name);
player = SimplePlayer.fromInputStream(inputStream);
}


public AudioMarker(Player player, String name) {
super(name);
this.player = player;
}

public void play() {
player.play();
}
}
@@ -0,0 +1,44 @@
/*
* SPDX-License-Identifier: CC0-1.0
*
* Copyright 2018-2019 Will Sargent.
*
* Licensed under the CC0 Public Domain Dedication;
* You may obtain a copy of the License at
*
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package com.tersesystems.logback.audio;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import com.tersesystems.logback.audio.Player;
import org.slf4j.Marker;

import java.util.Iterator;

public class AudioMarkerAppender extends AppenderBase<ILoggingEvent> {

@Override
protected void append(ILoggingEvent eventObject) {
writePlayerMarkerIfNecessary(eventObject.getMarker());
}

private void writePlayerMarkerIfNecessary(Marker marker) {
if (marker != null) {
if (isPlayerMarker(marker)) {
((Player) marker).play();
}

if (marker.hasReferences()) {
for (Iterator<Marker> i = marker.iterator(); i.hasNext();) {
writePlayerMarkerIfNecessary(i.next());
}
}
}
}

private static boolean isPlayerMarker(Marker marker) {
return marker instanceof Player;
}
}
@@ -0,0 +1,59 @@
/*
* SPDX-License-Identifier: CC0-1.0
*
* Copyright 2018-2019 Will Sargent.
*
* Licensed under the CC0 Public Domain Dedication;
* You may obtain a copy of the License at
*
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package com.tersesystems.logback.audio;

import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.spi.LifeCycle;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FilePlayer extends ContextAwareBase implements Player, LifeCycle {

private String file;
private Path path;
private volatile boolean started = false;

public FilePlayer() {
}

public void setFile(String file) {
this.file = file;
}

@Override
public void play() {
SimplePlayer.fromPath(path).play();
}

@Override
public void start() {
path = Paths.get(file);
if (Files.exists(path)) {
started = true;
} else {
addError(String.format("Path %s does not exist!", path));
started = false;
}
}

@Override
public void stop() {
path = null;
started = false;
}

@Override
public boolean isStarted() {
return started;
}
}
@@ -0,0 +1,47 @@
/*
* SPDX-License-Identifier: CC0-1.0
*
* Copyright 2018-2019 Will Sargent.
*
* Licensed under the CC0 Public Domain Dedication;
* You may obtain a copy of the License at
*
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package com.tersesystems.logback.audio;

import javax.sound.sampled.*;
import java.io.IOException;
import java.util.function.Supplier;

public interface PlayMethods {

default void play(Supplier<AudioInputStream> supplier) {
// https://docs.oracle.com/javase/tutorial/sound/playing.html
try (final AudioInputStream in = supplier.get()) {
AudioFormat baseFormat = in.getFormat();
AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
baseFormat.getSampleRate(),
16,
baseFormat.getChannels(),
baseFormat.getChannels() * 2,
baseFormat.getSampleRate(),
false);
try (final AudioInputStream dataIn = AudioSystem.getAudioInputStream(targetFormat, in)) {
DataLine.Info info = new DataLine.Info(Clip.class, targetFormat);
Clip clip = (Clip) AudioSystem.getLine(info);
if (clip != null) {
clip.addLineListener(event -> {
if (event.getType() == LineEvent.Type.STOP)
clip.close();
});

clip.open(dataIn);
clip.start();
}
}
} catch (LineUnavailableException | IOException e) {
throw new IllegalStateException(e);
}
}
}
@@ -0,0 +1,15 @@
/*
* SPDX-License-Identifier: CC0-1.0
*
* Copyright 2018-2019 Will Sargent.
*
* Licensed under the CC0 Public Domain Dedication;
* You may obtain a copy of the License at
*
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package com.tersesystems.logback.audio;

public interface Player {
void play();
}
@@ -0,0 +1,81 @@
/*
* SPDX-License-Identifier: CC0-1.0
*
* Copyright 2018-2019 Will Sargent.
*
* Licensed under the CC0 Public Domain Dedication;
* You may obtain a copy of the License at
*
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package com.tersesystems.logback.audio;

import ch.qos.logback.core.Context;
import ch.qos.logback.core.joran.action.Action;
import ch.qos.logback.core.joran.spi.ActionException;
import ch.qos.logback.core.joran.spi.InterpretationContext;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.spi.LifeCycle;
import ch.qos.logback.core.util.OptionHelper;
import org.xml.sax.Attributes;

public class PlayerAction extends Action {
Player player;
private boolean inError = false;

@Override
public void begin(InterpretationContext ic, String localName, Attributes attributes) throws ActionException {
Object o = ic.peekObject();

if (!(o instanceof PlayerAttachable)) {
String errMsg = "Could not find an CensorAttachable at the top of execution stack. Near [" + localName + "] line " + getLineNumber(ic);
inError = true;
addInfo(errMsg); // This can trigger in an "if" block from janino, so it may not be serious...
return;
}

PlayerAttachable playerAttachable = (PlayerAttachable) o;

String className = attributes.getValue(CLASS_ATTRIBUTE);
if (OptionHelper.isEmpty(className)) {
addError("Missing class name for player. Near [" + localName + "] line " + getLineNumber(ic));
inError = true;
return;
}

try {
addInfo("About to instantiate player of type [" + className + "]");
player = (Player) OptionHelper.instantiateByClassName(className, Player.class, context);

Context icContext = ic.getContext();
if (player instanceof ContextAwareBase) {
((ContextAwareBase) player).setContext(icContext);
}

ic.pushObject(player);
} catch (Exception oops) {
inError = true;
addError("Could not create player.", oops);
throw new ActionException(oops);
}
playerAttachable.addPlayer(player);
}

@Override
public void end(InterpretationContext ic, String name) throws ActionException {
if (inError) {
return;
}

if (player instanceof LifeCycle) {
((LifeCycle) player).start();
}

Object o = ic.peekObject();
if (o != player) {
addWarn("The object at the end of the stack is not the player pushed earlier.");
} else {
ic.popObject();
}
}
}
@@ -0,0 +1,17 @@
/*
* SPDX-License-Identifier: CC0-1.0
*
* Copyright 2018-2019 Will Sargent.
*
* Licensed under the CC0 Public Domain Dedication;
* You may obtain a copy of the License at
*
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package com.tersesystems.logback.audio;

public interface PlayerAttachable {
void addPlayer(Player player);

void clearAllPlayers();
}

0 comments on commit a68959b

Please sign in to comment.