diff --git a/client/pom.xml b/client/pom.xml
index 1d2614a710d7c90c109fbc30d7b23ff830943cd9..2def3a12bb55290f1e64ad27dbbf05688158b5b9 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <groupId>org.red5</groupId>
         <artifactId>red5-parent</artifactId>
-        <version>1.3.29</version>
+        <version>1.3.30</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>red5-client</artifactId>
diff --git a/client/src/main/java/org/red5/client/Red5Client.java b/client/src/main/java/org/red5/client/Red5Client.java
index 5d168b8259a47b3cfacfbf637a7ba356946db8ab..2c9f618019c0ddbc8e58af1cbf3f71244f8de42f 100644
--- a/client/src/main/java/org/red5/client/Red5Client.java
+++ b/client/src/main/java/org/red5/client/Red5Client.java
@@ -18,7 +18,7 @@ public final class Red5Client {
     /**
      * Current server version with revision
      */
-    public static final String VERSION = "Red5 Client 1.3.29";
+    public static final String VERSION = "Red5 Client 1.3.30";
 
     /**
      * Create a new Red5Client object using the connection local to the current thread A bit of magic that lets you access the red5 scope
diff --git a/client/src/main/java/org/red5/client/net/rtmp/BaseRTMPClientHandler.java b/client/src/main/java/org/red5/client/net/rtmp/BaseRTMPClientHandler.java
index c18f9f8e51cb73ca2425c2bfa41f0b8af8a30437..a3e7867f0c98e55ec7709786cd8372a1076c5777 100644
--- a/client/src/main/java/org/red5/client/net/rtmp/BaseRTMPClientHandler.java
+++ b/client/src/main/java/org/red5/client/net/rtmp/BaseRTMPClientHandler.java
@@ -52,6 +52,7 @@ import org.red5.server.stream.OutputStream;
 import org.red5.server.stream.consumer.ConnectionConsumer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
 /**
  * Base class for clients (RTMP and RTMPT)
@@ -907,6 +908,15 @@ public abstract class BaseRTMPClientHandler extends BaseRTMPHandler implements I
     public void setConnection(RTMPConnection conn) {
         this.conn = conn;
         this.conn.setHandler(this);
+        if (conn.getExecutor() == null) {
+            // setup executor
+            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+            executor.setCorePoolSize(1);
+            executor.setDaemon(true);
+            executor.setMaxPoolSize(1);
+            executor.initialize();
+            conn.setExecutor(executor);
+        }
     }
 
     /**
diff --git a/client/src/main/java/org/red5/client/net/rtmp/RTMPClientConnManager.java b/client/src/main/java/org/red5/client/net/rtmp/RTMPClientConnManager.java
index 8bdc4ad71cf260957fb84ab11e5aa3601b8f1045..b9e5d08c0c9632f69ecc9fd3d5c03455ee4ccdc2 100644
--- a/client/src/main/java/org/red5/client/net/rtmp/RTMPClientConnManager.java
+++ b/client/src/main/java/org/red5/client/net/rtmp/RTMPClientConnManager.java
@@ -42,7 +42,7 @@ public class RTMPClientConnManager implements IConnectionManager<BaseConnection>
     private static int executorQueueCapacity = 32;
 
     // whether or not to use the ThreadPoolTaskExecutor for incoming messages
-    protected static boolean enableTaskExecutor;
+    protected static boolean enableTaskExecutor = true;
 
     protected static IConnectionManager<BaseConnection> instance;
 
diff --git a/common/pom.xml b/common/pom.xml
index 713c995de6dc23a50e5fc982d8748afc0450d3e1..1ea3a1e97da8551b3c5940dbc05e9ef656cfbd06 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <groupId>org.red5</groupId>
         <artifactId>red5-parent</artifactId>
-        <version>1.3.29</version>
+        <version>1.3.30</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>red5-server-common</artifactId>
@@ -113,7 +113,7 @@
         <dependency>
             <groupId>net.engio</groupId>
             <artifactId>mbassador</artifactId>
-            <version>1.3.29</version>
+            <version>1.3.30</version>
         </dependency> -->
         <dependency>
             <groupId>junit</groupId>
diff --git a/common/src/main/java/org/red5/logging/LoggingContextSelector.java b/common/src/main/java/org/red5/logging/LoggingContextSelector.java
index 8fe6edd1db18d8c53031f9c7bbba836e51205529..37390b9bee6bd8ba9e0ecfe2d4dc409f661089d0 100644
--- a/common/src/main/java/org/red5/logging/LoggingContextSelector.java
+++ b/common/src/main/java/org/red5/logging/LoggingContextSelector.java
@@ -8,7 +8,6 @@
 package org.red5.logging;
 
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -37,7 +36,7 @@ public class LoggingContextSelector implements ContextSelector {
 
     private static final Semaphore lock = new Semaphore(1, true);
 
-    private static final ConcurrentMap<String, LoggerContext> contextMap = new ConcurrentHashMap<>(6, 0.9f, 1);
+    private static final ConcurrentMap<String, LoggerContext> contextMap = new ConcurrentHashMap<>();
 
     private static LoggerContext DEFAULT_CONTEXT;
 
@@ -194,9 +193,7 @@ public class LoggingContextSelector implements ContextSelector {
     }
 
     public List<String> getContextNames() {
-        List<String> list = new ArrayList<>();
-        list.addAll(contextMap.keySet());
-        return list;
+        return List.copyOf(contextMap.keySet());
     }
 
     public void setContextConfigFile(String contextConfigFile) {
diff --git a/common/src/main/java/org/red5/logging/Red5LoggerFactory.java b/common/src/main/java/org/red5/logging/Red5LoggerFactory.java
index e5b3bbaa97892b4a4e3efae6ed2b4de29df256d0..dd74ad112c7e25a00c09c62338b3ce46915907d5 100644
--- a/common/src/main/java/org/red5/logging/Red5LoggerFactory.java
+++ b/common/src/main/java/org/red5/logging/Red5LoggerFactory.java
@@ -29,11 +29,20 @@ public class Red5LoggerFactory {
 
     public static boolean DEBUG = true;
 
+    // root logger
+    private static Logger rootLogger;
+
+    // context selector
+    private static ContextSelector contextSelector;
+
     static {
         DEBUG = Boolean.valueOf(System.getProperty("logback.debug", "false"));
         try {
-            Logger logger = LoggerFactory.getILoggerFactory().getLogger(Logger.ROOT_LOGGER_NAME);
-            logger.debug("Red5LoggerFactory instanced by Thread: {}", Thread.currentThread().getName());
+            rootLogger = LoggerFactory.getILoggerFactory().getLogger(Logger.ROOT_LOGGER_NAME);
+            rootLogger.debug("Red5LoggerFactory instanced by Thread: {}", Thread.currentThread().getName());
+            rootLogger.debug("Logging context selector: {} impl: {}", System.getProperty("logback.ContextSelector"), getContextSelector());
+            // get the context selector here
+            contextSelector = getContextSelector();
         } catch (Throwable t) {
             t.printStackTrace();
         }
@@ -43,7 +52,7 @@ public class Red5LoggerFactory {
         if (DEBUG) {
             System.out.printf("getLogger for: %s thread: %s%n", clazz.getName(), Thread.currentThread().getName());
             ClassLoader cl = Thread.currentThread().getContextClassLoader();
-            System.out.printf("class loader: %s%n", cl);
+            rootLogger.debug("Class loader: {}", cl);
             // if cl is WebappClassLoader type we can probably get the context from it
             //if (cl instanceof WebappClassLoader) {
             //    getContextName()
@@ -52,28 +61,14 @@ public class Red5LoggerFactory {
         Logger logger = null;
         if (useLogback) {
             // determine the red5 app name or servlet context name
-            String contextName = CoreConstants.DEFAULT_CONTEXT_NAME;
+            final String threadName = Thread.currentThread().getName();
             // route the Launcher entries to the correct context
-            String[] parts = Thread.currentThread().getName().split("Loader:/");
-            if (parts.length > 1) {
-                contextName = parts[1];
+            if (threadName.startsWith("Loader:/")) {
+                String contextName = threadName.split("Loader:/")[1];
+                logger = getLogger(clazz, contextName);
+            } else {
+                logger = getLogger(clazz, CoreConstants.DEFAULT_CONTEXT_NAME);
             }
-            logger = Red5LoggerFactory.getLogger(clazz, contextName);
-            /*
-             * // get a reference to our caller Class caller = Thread.currentThread().getStackTrace()[2].getClassName(); if (DEBUG) { System.out.printf("Caller class: %s classloader: %s%n",
-             * caller, caller.getClassLoader()); } // if the incoming class extends StatefulScopeWrappingAdapter we lookup the context by scope name boolean scopeAware =
-             * StatefulScopeWrappingAdapter.class.isAssignableFrom(caller); if (DEBUG) { System.out.printf("scopeAware: %s%n", scopeAware); } if (scopeAware) { try { Class wrapper = null; if
-             * ((wrapper = caller.asSubclass(StatefulScopeWrappingAdapter.class)) != null) { Method getScope = wrapper.getMethod("getScope", new Class[0]); // NPE will occur here if the scope
-             * is not yet set on the application adapter IScope scope = (IScope) getScope.invoke(null, new Object[0]); if (DEBUG) { System.out.printf("scope: %s%n", scope); } contextName =
-             * scope.getName(); } } catch (Exception cce) { //cclog.warn("Exception {}", e); } } else { // if the incoming class is a servlet we lookup the context name boolean
-             * servletScopeAware = Servlet.class.isAssignableFrom(caller); if (DEBUG) { System.out.printf("servletScopeAware: %s%n", servletScopeAware); } if (servletScopeAware) { try { Class
-             * wrapper = null; if ((wrapper = caller.asSubclass(Servlet.class)) != null) { //ServletConfig getServletConfig Method getServletConfig = wrapper.getMethod("getServletConfig", new
-             * Class[0]); // NPE will occur here if the scope is not yet set on the application adapter ServletConfig config = (ServletConfig) getServletConfig.invoke(null, new Object[0]); if
-             * (DEBUG) { System.out.printf("config: %s%n", config); } contextName = config.getServletContext().getContextPath().replaceAll("/", ""); if ("".equals(contextName)) { contextName =
-             * "root"; } } } catch (Exception cce) { //cclog.warn("Exception {}", e); } } else { // route the Launcher entries to the correct context String[] parts =
-             * Thread.currentThread().getName().split("Loader:/"); if (parts.length > 1) { contextName = parts[1]; } else { contextName = CoreConstants.DEFAULT_CONTEXT_NAME; } } } logger =
-             * Red5LoggerFactory.getLogger(clazz, contextName);
-             */
         }
         if (logger == null) {
             logger = LoggerFactory.getLogger(clazz);
@@ -81,8 +76,7 @@ public class Red5LoggerFactory {
         return logger;
     }
 
-    @SuppressWarnings({ "rawtypes" })
-    public static Logger getLogger(Class clazz, String contextName) {
+    public static Logger getLogger(Class<?> clazz, String contextName) {
         return getLogger(clazz.getName(), contextName);
     }
 
@@ -97,9 +91,8 @@ public class Red5LoggerFactory {
                 contextName = CoreConstants.DEFAULT_CONTEXT_NAME;
             }
             try {
-                ContextSelector selector = Red5LoggerFactory.getContextSelector();
                 // get the context for the given context name or default if null
-                LoggerContext context = selector.getLoggerContext(contextName);
+                LoggerContext context = contextSelector.getLoggerContext(contextName);
                 // and if we get here, fall back to the default context
                 if (context == null) {
                     System.err.printf("No context named %s was found!!%n", contextName);
@@ -107,12 +100,13 @@ public class Red5LoggerFactory {
                 // get the logger from the context or default context
                 if (context != null) {
                     logger = context.getLogger(name);
-                    //                    System.out.printf("Application name: %s in context: %s%n", context.getProperty(KEY_APP_NAME), contextName);
+                    if (DEBUG) {
+                        rootLogger.debug("Application name: {} in context: {}", context.getProperty(CoreConstants.CONTEXT_NAME_KEY), contextName);
+                    }
                 }
             } catch (Exception e) {
                 // no logback, use whatever logger is in-place
-                System.err.printf("Exception %s%n", e.getMessage());
-                e.printStackTrace();
+                rootLogger.error("Exception {}", e);
             }
         }
         if (logger == null) {
@@ -122,26 +116,26 @@ public class Red5LoggerFactory {
     }
 
     public static ContextSelector getContextSelector() {
+        ContextSelector selector = null;
         if (useLogback) {
             ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
-            ContextSelector selector = contextSelectorBinder.getContextSelector();
+            selector = contextSelectorBinder.getContextSelector();
             if (selector == null) {
                 if (DEBUG) {
-                    System.err.println("Context selector was null, creating default context");
+                    rootLogger.error("Context selector was null, creating default context");
                 }
                 LoggerContext defaultLoggerContext = new LoggerContext();
                 defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
                 try {
                     contextSelectorBinder.init(defaultLoggerContext, null);
                     selector = contextSelectorBinder.getContextSelector();
+                    rootLogger.debug("Context selector: {}", selector.getClass().getName());
                 } catch (Exception e) {
-                    e.printStackTrace();
+                    rootLogger.error("Exception {}", e);
                 }
             }
-            //System.out.printf("Context selector: %s%n", selector.getClass().getName());
-            return selector;
         }
-        return null;
+        return selector;
     }
 
     public static void setUseLogback(boolean useLogback) {
diff --git a/common/src/main/java/org/red5/server/api/Red5.java b/common/src/main/java/org/red5/server/api/Red5.java
index a2dc5dc3d5caf138cec1ae9f239eae6d3825eb99..a7da45f8a7c84b226f92a40b154553599ac70097 100644
--- a/common/src/main/java/org/red5/server/api/Red5.java
+++ b/common/src/main/java/org/red5/server/api/Red5.java
@@ -57,12 +57,12 @@ public final class Red5 {
     /**
      * Server version with revision
      */
-    public static final String VERSION = "Red5 Server 1.3.29";
+    public static final String VERSION = "Red5 Server 1.3.30";
 
     /**
      * Server version for fmsVer requests
      */
-    public static final String FMS_VERSION = "RED5/1,3,29,0";
+    public static final String FMS_VERSION = "RED5/1,3,30,0";
 
     /**
      * Server capabilities
diff --git a/common/src/main/java/org/red5/server/net/rtmp/RTMPConnection.java b/common/src/main/java/org/red5/server/net/rtmp/RTMPConnection.java
index ef39328295355fa7dc2071054201ab8ae80aba3b..c2ceefa17576c897ade9ec39e0458c56bf7271d1 100755
--- a/common/src/main/java/org/red5/server/net/rtmp/RTMPConnection.java
+++ b/common/src/main/java/org/red5/server/net/rtmp/RTMPConnection.java
@@ -1483,10 +1483,9 @@ public abstract class RTMPConnection extends BaseConnection implements IStreamCa
                                 }
                                 log.info("Rejected task: {}", task);
                             } catch (Throwable e) {
-                                log.error("Incoming message failed task: {}", task, e);
+                                log.warn("Incoming message failed task: {}", task, e);
                                 if (isDebug) {
-                                    log.debug("Execution rejected on {} - {}", getSessionId(), RTMP.states[getStateCode()]);
-                                    log.debug("Lock permits - decode: {} encode: {}", decoderLock.availablePermits(), encoderLock.availablePermits());
+                                    log.debug("Execution rejected on {} - {} lock permits - decode: {} encode: {}", getSessionId(), RTMP.states[getStateCode()], decoderLock.availablePermits(), encoderLock.availablePermits());
                                 }
                             }
                         }
diff --git a/common/src/main/java/org/red5/server/stream/ClientBroadcastStream.java b/common/src/main/java/org/red5/server/stream/ClientBroadcastStream.java
index 9f2bed669dd74961c01823c5aa92f5b82c18025a..61b1491205bf34c3eef8f7246bf0a6392d23611e 100644
--- a/common/src/main/java/org/red5/server/stream/ClientBroadcastStream.java
+++ b/common/src/main/java/org/red5/server/stream/ClientBroadcastStream.java
@@ -7,6 +7,11 @@
 
 package org.red5.server.stream;
 
+import static org.red5.server.net.rtmp.message.Constants.TYPE_AUDIO_DATA;
+import static org.red5.server.net.rtmp.message.Constants.TYPE_INVOKE;
+import static org.red5.server.net.rtmp.message.Constants.TYPE_NOTIFY;
+import static org.red5.server.net.rtmp.message.Constants.TYPE_VIDEO_DATA;
+
 import java.io.File;
 import java.io.IOException;
 import java.lang.management.ManagementFactory;
@@ -65,7 +70,6 @@ import org.red5.server.messaging.OOBControlMessage;
 import org.red5.server.messaging.PipeConnectionEvent;
 import org.red5.server.net.rtmp.event.AudioData;
 import org.red5.server.net.rtmp.event.IRTMPEvent;
-import org.red5.server.net.rtmp.event.Invoke;
 import org.red5.server.net.rtmp.event.Notify;
 import org.red5.server.net.rtmp.event.VideoData;
 import org.red5.server.net.rtmp.message.Constants;
@@ -97,6 +101,8 @@ public class ClientBroadcastStream extends AbstractClientStream implements IClie
 
     private static final Logger log = LoggerFactory.getLogger(ClientBroadcastStream.class);
 
+    private static final boolean isDebug = log.isDebugEnabled();
+
     /**
      * Whether or not to automatically record the associated stream.
      */
@@ -264,7 +270,7 @@ public class ClientBroadcastStream extends AbstractClientStream implements IClie
                 case STREAM_CONTROL:
                 case STREAM_DATA:
                     // create the event
-                    IRTMPEvent rtmpEvent;
+                    final IRTMPEvent rtmpEvent;
                     try {
                         rtmpEvent = (IRTMPEvent) event;
                     } catch (ClassCastException e) {
@@ -276,13 +282,7 @@ public class ClientBroadcastStream extends AbstractClientStream implements IClie
                     if (rtmpEvent.getSourceType() != Constants.SOURCE_TYPE_LIVE) {
                         rtmpEvent.setSourceType(Constants.SOURCE_TYPE_LIVE);
                     }
-                    /*
-                     * if (log.isTraceEnabled()) { // If this is first packet save its timestamp; expect it is // absolute? no matter: it's never used! if (firstPacketTime == -1) { firstPacketTime =
-                     * rtmpEvent.getTimestamp(); log.trace(String.format("CBS=@%08x: rtmpEvent=%s creation=%s firstPacketTime=%d", System.identityHashCode(this), rtmpEvent.getClass().getSimpleName(),
-                     * creationTime, firstPacketTime)); } else { log.trace(String.format("CBS=@%08x: rtmpEvent=%s creation=%s firstPacketTime=%d timestamp=%d", System.identityHashCode(this),
-                     * rtmpEvent.getClass().getSimpleName(), creationTime, firstPacketTime, eventTime)); } }
-                     */
-                    //get the buffer only once per call
+                    // get the buffer only once per call
                     IoBuffer buf = null;
                     if (rtmpEvent instanceof IStreamData && (buf = ((IStreamData<?>) rtmpEvent).getData()) != null) {
                         bytesReceived += buf.limit();
@@ -294,65 +294,71 @@ public class ClientBroadcastStream extends AbstractClientStream implements IClie
                         info = (StreamCodecInfo) codecInfo;
                     }
                     //log.trace("Stream codec info: {}", info);
-                    if (rtmpEvent instanceof AudioData) {
-                        //log.trace("Audio: {}", eventTime);
-                        IAudioStreamCodec audioStreamCodec = null;
-                        if (checkAudioCodec) {
-                            // dont try to read codec info from 0 length audio packets
-                            if (buf.limit() > 0) {
-                                audioStreamCodec = AudioCodecFactory.getAudioCodec(buf);
+                    switch (rtmpEvent.getDataType()) {
+                        case TYPE_AUDIO_DATA: // AudioData
+                            //log.trace("Audio: {}", eventTime);
+                            IAudioStreamCodec audioStreamCodec = null;
+                            if (checkAudioCodec) {
+                                // dont try to read codec info from 0 length audio packets
+                                if (buf.limit() > 0) {
+                                    audioStreamCodec = AudioCodecFactory.getAudioCodec(buf);
+                                    if (info != null) {
+                                        info.setAudioCodec(audioStreamCodec);
+                                    }
+                                    checkAudioCodec = false;
+                                }
+                            } else if (codecInfo != null) {
+                                audioStreamCodec = codecInfo.getAudioCodec();
+                            }
+                            if (audioStreamCodec != null) {
+                                audioStreamCodec.addData(buf);
+                            }
+                            if (info != null) {
+                                info.setHasAudio(true);
+                            }
+                            break;
+                        case TYPE_VIDEO_DATA: // VideoData
+                            //log.trace("Video: {}", eventTime);
+                            IVideoStreamCodec videoStreamCodec = null;
+                            if (checkVideoCodec) {
+                                videoStreamCodec = VideoCodecFactory.getVideoCodec(buf);
                                 if (info != null) {
-                                    info.setAudioCodec(audioStreamCodec);
+                                    info.setVideoCodec(videoStreamCodec);
                                 }
-                                checkAudioCodec = false;
+                                checkVideoCodec = false;
+                            } else if (codecInfo != null) {
+                                videoStreamCodec = codecInfo.getVideoCodec();
+                            }
+                            if (videoStreamCodec != null) {
+                                videoStreamCodec.addData(buf, eventTime);
                             }
-                        } else if (codecInfo != null) {
-                            audioStreamCodec = codecInfo.getAudioCodec();
-                        }
-                        if (audioStreamCodec != null) {
-                            audioStreamCodec.addData(buf);
-                        }
-                        if (info != null) {
-                            info.setHasAudio(true);
-                        }
-                    } else if (rtmpEvent instanceof VideoData) {
-                        //log.trace("Video: {}", eventTime);
-                        IVideoStreamCodec videoStreamCodec = null;
-                        if (checkVideoCodec) {
-                            videoStreamCodec = VideoCodecFactory.getVideoCodec(buf);
                             if (info != null) {
-                                info.setVideoCodec(videoStreamCodec);
+                                info.setHasVideo(true);
                             }
-                            checkVideoCodec = false;
-                        } else if (codecInfo != null) {
-                            videoStreamCodec = codecInfo.getVideoCodec();
-                        }
-                        if (videoStreamCodec != null) {
-                            videoStreamCodec.addData(buf, eventTime);
-                        }
-                        if (info != null) {
-                            info.setHasVideo(true);
-                        }
-                    } else if (rtmpEvent instanceof Invoke) {
-                        //Invoke invokeEvent = (Invoke) rtmpEvent;
-                        //log.debug("Invoke action: {}", invokeEvent.getAction());
-                        // event / stream listeners will not be notified of invokes
-                        return;
-                    } else if (rtmpEvent instanceof Notify) {
-                        Notify notifyEvent = (Notify) rtmpEvent;
-                        String action = notifyEvent.getAction();
-                        //if (log.isDebugEnabled()) {
-                        //log.debug("Notify action: {}", action);
-                        //}
-                        if ("onMetaData".equals(action)) {
-                            // store the metadata
-                            try {
-                                //log.debug("Setting metadata");
-                                setMetaData(notifyEvent.duplicate());
-                            } catch (Exception e) {
-                                log.warn("Metadata could not be duplicated for this stream", e);
+                            break;
+                        case TYPE_NOTIFY:
+                            Notify notifyEvent = (Notify) rtmpEvent;
+                            String action = notifyEvent.getAction();
+                            //if (isDebug) {
+                            //log.debug("Notify action: {}", action);
+                            //}
+                            if ("onMetaData".equals(action)) {
+                                // store the metadata
+                                try {
+                                    //log.debug("Setting metadata");
+                                    setMetaData(notifyEvent.duplicate());
+                                } catch (Exception e) {
+                                    log.warn("Metadata could not be duplicated for this stream", e);
+                                }
                             }
-                        }
+                            break;
+                        case TYPE_INVOKE:
+                            //Invoke invokeEvent = (Invoke) rtmpEvent;
+                            //log.debug("Invoke action: {}", invokeEvent.getAction());
+                            // event / stream listeners will not be notified of invokes
+                            return;
+                        default:
+                            log.debug("Unknown: {}", rtmpEvent);
                     }
                     // update last event time
                     if (eventTime > latestTimeStamp) {
@@ -367,7 +373,7 @@ public class ClientBroadcastStream extends AbstractClientStream implements IClie
                             // create new RTMP message, initialize it and push through pipe
                             RTMPMessage msg = RTMPMessage.build(rtmpEvent, eventTime);
                             livePipe.pushMessage(msg);
-                        } else if (log.isDebugEnabled()) {
+                        } else if (isDebug) {
                             log.debug("Live pipe was null, message was not pushed");
                         }
                     } catch (IOException err) {
@@ -379,7 +385,7 @@ public class ClientBroadcastStream extends AbstractClientStream implements IClie
                             try {
                                 listener.packetReceived(this, (IStreamPacket) rtmpEvent);
                             } catch (Exception e) {
-                                log.error("Error while notifying listener {}", listener, e);
+                                log.warn("Error while notifying listener {}", listener, e);
                                 if (listener instanceof RecordingListener) {
                                     sendRecordFailedNotify(e.getMessage());
                                 }
@@ -389,10 +395,14 @@ public class ClientBroadcastStream extends AbstractClientStream implements IClie
                     break;
                 default:
                     // ignored event
-                    //log.debug("Ignoring event: {}", event.getType());
+                    if (isDebug) {
+                        log.debug("Ignoring event: {}", event.getType());
+                    }
             }
         } else {
-            log.debug("Event was of wrong type or stream is closed ({})", closed);
+            if (isDebug) {
+                log.debug("Event was of wrong type or stream is closed ({})", closed);
+            }
         }
     }
 
@@ -551,7 +561,7 @@ public class ClientBroadcastStream extends AbstractClientStream implements IClie
         params.put("creationdate", ZonedDateTime.ofInstant(cal.toInstant(), ZoneId.of("UTC")).format(DateTimeFormatter.ISO_INSTANT));
         cal.setTimeInMillis(startTime);
         params.put("startdate", ZonedDateTime.ofInstant(cal.toInstant(), ZoneId.of("UTC")).format(DateTimeFormatter.ISO_INSTANT));
-        if (log.isDebugEnabled()) {
+        if (isDebug) {
             log.debug("Params: {}", params);
         }
         out.writeMap(params);
@@ -627,7 +637,7 @@ public class ClientBroadcastStream extends AbstractClientStream implements IClie
                 break;
             case PROVIDER_DISCONNECT:
                 //log.debug("Provider disconnect");
-                //if (log.isDebugEnabled() && livePipe != null) {
+                //if (isDebug && livePipe != null) {
                 //log.debug("Provider: {}", livePipe.getClass().getName());
                 //}
                 if (livePipe == event.getSource()) {
@@ -637,7 +647,7 @@ public class ClientBroadcastStream extends AbstractClientStream implements IClie
             case CONSUMER_CONNECT_PUSH:
                 //log.debug("Consumer connect");
                 IPipe pipe = (IPipe) event.getSource();
-                //if (log.isDebugEnabled() && pipe != null) {
+                //if (isDebug && pipe != null) {
                 //log.debug("Consumer: {}", pipe.getClass().getName());
                 //}
                 if (livePipe == pipe) {
diff --git a/io/pom.xml b/io/pom.xml
index c97c00f29096c2f658e821cf8f306a635259de0b..85f9c267d55ab29394fb81088ed4094d848984a8 100644
--- a/io/pom.xml
+++ b/io/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <groupId>org.red5</groupId>
         <artifactId>red5-parent</artifactId>
-        <version>1.3.29</version>
+        <version>1.3.30</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>red5-io</artifactId>
diff --git a/io/src/main/java/org/red5/codec/AACAudio.java b/io/src/main/java/org/red5/codec/AACAudio.java
index 4f05729f6df3a4e26354c7244dde9926674ed00a..122ac7a6cd3ef4f241b22b0df9503374715e9f9b 100644
--- a/io/src/main/java/org/red5/codec/AACAudio.java
+++ b/io/src/main/java/org/red5/codec/AACAudio.java
@@ -26,11 +26,6 @@ public class AACAudio extends AbstractAudio {
 
     public static final int[] AAC_SAMPLERATES = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 };
 
-    /**
-     * AAC audio codec constant
-     */
-    static final String CODEC_NAME = "AAC";
-
     /**
      * Block of data (AAC DecoderConfigurationRecord)
      */
@@ -38,13 +33,14 @@ public class AACAudio extends AbstractAudio {
 
     /** Constructs a new AACAudio */
     public AACAudio() {
+        codec = AudioCodec.AAC;
         this.reset();
     }
 
     /** {@inheritDoc} */
     @Override
     public String getName() {
-        return CODEC_NAME;
+        return codec.name();
     }
 
     /** {@inheritDoc} */
diff --git a/io/src/main/java/org/red5/codec/AVCVideo.java b/io/src/main/java/org/red5/codec/AVCVideo.java
index 9fa1bda0f46550b0e5b6438fbde1bf322f576283..9b504a2da56c19abb533a87b65d1dc8c8909d7cd 100644
--- a/io/src/main/java/org/red5/codec/AVCVideo.java
+++ b/io/src/main/java/org/red5/codec/AVCVideo.java
@@ -7,9 +7,6 @@
 
 package org.red5.codec;
 
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicInteger;
-
 import org.apache.mina.core.buffer.IoBuffer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -24,40 +21,17 @@ public class AVCVideo extends AbstractVideo {
 
     private static Logger log = LoggerFactory.getLogger(AVCVideo.class);
 
-    /**
-     * AVC video codec constant
-     */
-    static final String CODEC_NAME = "AVC";
+    private static boolean isDebug = log.isDebugEnabled();
 
     /** Video decoder configuration data */
     private FrameData decoderConfiguration;
 
-    /**
-     * Storage for frames buffered since last key frame
-     */
-    private final CopyOnWriteArrayList<FrameData> interframes = new CopyOnWriteArrayList<>();
-
-    /**
-     * Number of frames buffered since last key frame
-     */
-    private final AtomicInteger numInterframes = new AtomicInteger(0);
-
-    /**
-     * Whether or not to buffer interframes
-     */
-    private boolean bufferInterframes = false;
-
     /** Constructs a new AVCVideo. */
     public AVCVideo() {
+        codec = VideoCodec.AVC;
         this.reset();
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public String getName() {
-        return CODEC_NAME;
-    }
-
     /** {@inheritDoc} */
     @Override
     public boolean canDropFrames() {
@@ -109,8 +83,7 @@ public class AVCVideo extends AbstractVideo {
             if ((frameType & 0x0f) == VideoCodec.AVC.getId()) {
                 // check for keyframe
                 if ((frameType & 0xf0) == FLV_FRAME_KEY) {
-                    //log.trace("Key frame");
-                    if (log.isDebugEnabled()) {
+                    if (isDebug) {
                         log.debug("Keyframe - AVC type: {}", avcType);
                     }
                     // rewind
@@ -139,7 +112,7 @@ public class AVCVideo extends AbstractVideo {
                     //log.trace("Keyframes: {}", keyframes.size());
                 } else if (bufferInterframes) {
                     //log.trace("Interframe");
-                    if (log.isDebugEnabled()) {
+                    if (isDebug) {
                         log.debug("Interframe - AVC type: {}", avcType);
                     }
                     // rewind
@@ -153,7 +126,7 @@ public class AVCVideo extends AbstractVideo {
                             interframes.add(new FrameData(data));
                         }
                     } catch (Throwable e) {
-                        log.error("Failed to buffer interframe", e);
+                        log.warn("Failed to buffer interframe", e);
                     }
                     //log.trace("Interframes: {}", interframes.size());
                 }
@@ -176,27 +149,4 @@ public class AVCVideo extends AbstractVideo {
         return decoderConfiguration.getFrame();
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public int getNumInterframes() {
-        return numInterframes.get();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public FrameData getInterframe(int index) {
-        if (index < numInterframes.get()) {
-            return interframes.get(index);
-        }
-        return null;
-    }
-
-    public boolean isBufferInterframes() {
-        return bufferInterframes;
-    }
-
-    public void setBufferInterframes(boolean bufferInterframes) {
-        this.bufferInterframes = bufferInterframes;
-    }
-
 }
diff --git a/io/src/main/java/org/red5/codec/AbstractAudio.java b/io/src/main/java/org/red5/codec/AbstractAudio.java
index 6cd7c80f8d1fe68e8820967ebbfa8ab8cca0c03c..d7a4b36fe5461b023f9ce1a0e1a002690fa6a59c 100644
--- a/io/src/main/java/org/red5/codec/AbstractAudio.java
+++ b/io/src/main/java/org/red5/codec/AbstractAudio.java
@@ -4,9 +4,16 @@ import org.apache.mina.core.buffer.IoBuffer;
 
 public class AbstractAudio implements IAudioStreamCodec {
 
+    protected AudioCodec codec;
+
+    @Override
+    public AudioCodec getCodec() {
+        return codec;
+    }
+
     @Override
     public String getName() {
-        return null;
+        return codec.name();
     }
 
     @Override
diff --git a/io/src/main/java/org/red5/codec/AbstractVideo.java b/io/src/main/java/org/red5/codec/AbstractVideo.java
index 48e03e6529373e31e28850969c794b41c24acf8b..e0ccc7c4507e547232f7a8518d65eb7cf1325c17 100644
--- a/io/src/main/java/org/red5/codec/AbstractVideo.java
+++ b/io/src/main/java/org/red5/codec/AbstractVideo.java
@@ -1,12 +1,15 @@
 package org.red5.codec;
 
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.mina.core.buffer.IoBuffer;
 import org.red5.io.IoConstants;
 
 public class AbstractVideo implements IVideoStreamCodec, IoConstants {
 
+    protected VideoCodec codec;
+
     /** Current timestamp for the stored keyframe */
     protected int keyframeTimestamp;
 
@@ -15,9 +18,29 @@ public class AbstractVideo implements IVideoStreamCodec, IoConstants {
      */
     protected final CopyOnWriteArrayList<FrameData> keyframes = new CopyOnWriteArrayList<>();
 
+    /**
+     * Storage for frames buffered since last key frame
+     */
+    protected final CopyOnWriteArrayList<FrameData> interframes = new CopyOnWriteArrayList<>();
+
+    /**
+     * Number of frames buffered since last key frame
+     */
+    protected final AtomicInteger numInterframes = new AtomicInteger(0);
+
+    /**
+     * Whether or not to buffer interframes
+     */
+    protected boolean bufferInterframes = false;
+
+    @Override
+    public VideoCodec getCodec() {
+        return codec;
+    }
+
     @Override
     public String getName() {
-        return null;
+        return codec.name();
     }
 
     @Override
@@ -69,14 +92,27 @@ public class AbstractVideo implements IVideoStreamCodec, IoConstants {
         return keyframes.toArray(new FrameData[0]);
     }
 
+    /** {@inheritDoc} */
     @Override
     public int getNumInterframes() {
-        return 0;
+        return numInterframes.get();
     }
 
+    /** {@inheritDoc} */
     @Override
-    public FrameData getInterframe(int idx) {
+    public FrameData getInterframe(int index) {
+        if (index < numInterframes.get()) {
+            return interframes.get(index);
+        }
         return null;
     }
 
+    public boolean isBufferInterframes() {
+        return bufferInterframes;
+    }
+
+    public void setBufferInterframes(boolean bufferInterframes) {
+        this.bufferInterframes = bufferInterframes;
+    }
+
 }
diff --git a/io/src/main/java/org/red5/codec/AudioCodec.java b/io/src/main/java/org/red5/codec/AudioCodec.java
index 74de6e38d7bd098017a223f32e35702ebc5ded1b..3d72d7fba4479600142d63d51412b415cbe227d3 100644
--- a/io/src/main/java/org/red5/codec/AudioCodec.java
+++ b/io/src/main/java/org/red5/codec/AudioCodec.java
@@ -19,7 +19,7 @@ import java.util.Map;
  */
 public enum AudioCodec {
 
-    PCM((byte) 0), ADPCM((byte) 0x01), MP3((byte) 0x02), PCM_LE((byte) 0x03), NELLY_MOSER_16K((byte) 0x04), NELLY_MOSER_8K((byte) 0x05), NELLY_MOSER((byte) 0x06), PCM_ALAW((byte) 0x07), PCM_MULAW((byte) 0x08), RESERVED((byte) 0x09), AAC((byte) 0x0a), SPEEX((byte) 0x0b), MP2((byte) 0x0c), OPUS((byte) 0x0d), MP3_8K((byte) 0x0e), DEVICE_SPECIFIC((byte) 0x0f);
+    PCM((byte) 0), ADPCM((byte) 0x01), MP3((byte) 0x02), PCM_LE((byte) 0x03), NELLY_MOSER_16K((byte) 0x04), NELLY_MOSER_8K((byte) 0x05), NELLY_MOSER((byte) 0x06), PCM_ALAW((byte) 0x07), PCM_MULAW((byte) 0x08), L16((byte) 0x09), AAC((byte) 0x0a), SPEEX((byte) 0x0b), MP2((byte) 0x0c), OPUS((byte) 0x0d), MP3_8K((byte) 0x0e), DEVICE_SPECIFIC((byte) 0x0f);
 
     /**
      * Codecs which have private / config data or type identifiers included.
diff --git a/io/src/main/java/org/red5/codec/HEVCVideo.java b/io/src/main/java/org/red5/codec/HEVCVideo.java
index 12ded0e6241a7e4b57ece6fc0086e815f6b76b10..a01ed381f1e9842f1749ed77008024d98b87c364 100644
--- a/io/src/main/java/org/red5/codec/HEVCVideo.java
+++ b/io/src/main/java/org/red5/codec/HEVCVideo.java
@@ -7,9 +7,6 @@
 
 package org.red5.codec;
 
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicInteger;
-
 import org.apache.mina.core.buffer.IoBuffer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -23,40 +20,17 @@ public class HEVCVideo extends AbstractVideo {
 
     private static Logger log = LoggerFactory.getLogger(HEVCVideo.class);
 
-    /**
-     * HEVC video codec constant
-     */
-    static final String CODEC_NAME = "HEVC";
+    private static boolean isDebug = log.isDebugEnabled();
 
     /** Video decoder configuration data */
     private FrameData decoderConfiguration;
 
-    /**
-     * Storage for frames buffered since last key frame
-     */
-    private final CopyOnWriteArrayList<FrameData> interframes = new CopyOnWriteArrayList<>();
-
-    /**
-     * Number of frames buffered since last key frame
-     */
-    private final AtomicInteger numInterframes = new AtomicInteger(0);
-
-    /**
-     * Whether or not to buffer interframes
-     */
-    private boolean bufferInterframes = false;
-
     /** Constructs a new AVCVideo. */
     public HEVCVideo() {
+        codec = VideoCodec.HEVC;
         this.reset();
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public String getName() {
-        return CODEC_NAME;
-    }
-
     /** {@inheritDoc} */
     @Override
     public boolean canDropFrames() {
@@ -108,8 +82,7 @@ public class HEVCVideo extends AbstractVideo {
             if ((frameType & 0x0f) == VideoCodec.HEVC.getId()) {
                 // check for keyframe
                 if ((frameType & 0xf0) == FLV_FRAME_KEY) {
-                    //log.trace("Key frame");
-                    if (log.isDebugEnabled()) {
+                    if (isDebug) {
                         log.debug("Keyframe - HEVC type: {}", avcType);
                     }
                     // rewind
@@ -129,7 +102,9 @@ public class HEVCVideo extends AbstractVideo {
                             keyframes.add(new FrameData(data));
                             break;
                         case 0: // configuration
-                            //log.trace("Decoder configuration");
+                            if (isDebug) {
+                                log.debug("Decoder configuration");
+                            }
                             // Store HEVCDecoderConfigurationRecord data
                             decoderConfiguration.setData(data);
                             softReset();
@@ -138,7 +113,7 @@ public class HEVCVideo extends AbstractVideo {
                     //log.trace("Keyframes: {}", keyframes.size());
                 } else if (bufferInterframes) {
                     //log.trace("Interframe");
-                    if (log.isDebugEnabled()) {
+                    if (isDebug) {
                         log.debug("Interframe - HEVC type: {}", avcType);
                     }
                     // rewind
@@ -152,7 +127,7 @@ public class HEVCVideo extends AbstractVideo {
                             interframes.add(new FrameData(data));
                         }
                     } catch (Throwable e) {
-                        log.error("Failed to buffer interframe", e);
+                        log.warn("Failed to buffer interframe", e);
                     }
                     //log.trace("Interframes: {}", interframes.size());
                 }
@@ -175,27 +150,4 @@ public class HEVCVideo extends AbstractVideo {
         return decoderConfiguration.getFrame();
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public int getNumInterframes() {
-        return numInterframes.get();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public FrameData getInterframe(int index) {
-        if (index < numInterframes.get()) {
-            return interframes.get(index);
-        }
-        return null;
-    }
-
-    public boolean isBufferInterframes() {
-        return bufferInterframes;
-    }
-
-    public void setBufferInterframes(boolean bufferInterframes) {
-        this.bufferInterframes = bufferInterframes;
-    }
-
 }
diff --git a/io/src/main/java/org/red5/codec/IAudioStreamCodec.java b/io/src/main/java/org/red5/codec/IAudioStreamCodec.java
index 03f76623b7753b5a096dcfba07d85766218fe49b..9e82206122d5ee574ef6c7af8e3a4ff86931c786 100644
--- a/io/src/main/java/org/red5/codec/IAudioStreamCodec.java
+++ b/io/src/main/java/org/red5/codec/IAudioStreamCodec.java
@@ -16,15 +16,20 @@ import org.apache.mina.core.buffer.IoBuffer;
  */
 public interface IAudioStreamCodec {
 
+    /**
+     * @return the codec type.
+     */
+    AudioCodec getCodec();
+
     /**
      * @return the name of the audio codec.
      */
-    public String getName();
+    String getName();
 
     /**
      * Reset the codec to its initial state.
      */
-    public void reset();
+    void reset();
 
     /**
      * Returns true if the codec knows how to handle the passed stream data.
@@ -33,7 +38,7 @@ public interface IAudioStreamCodec {
      *            some sample data to see if this codec can handle it.
      * @return can this code handle the data.
      */
-    public boolean canHandleData(IoBuffer data);
+    boolean canHandleData(IoBuffer data);
 
     /**
      * Update the state of the codec with the passed data.
@@ -42,7 +47,7 @@ public interface IAudioStreamCodec {
      *            data to tell the codec we're adding
      * @return true for success. false for error.
      */
-    public boolean addData(IoBuffer data);
+    boolean addData(IoBuffer data);
 
     /**
      * Add audio data with a time stamp and a flag identifying the content as AMF or not.
@@ -52,13 +57,15 @@ public interface IAudioStreamCodec {
      * @param amf if true, data is in AMF format otherwise its most likely from non-AMF source like RTP
      * @return true if data is added and false otherwise
      */
-    public boolean addData(IoBuffer data, int timestamp, boolean amf);
+    boolean addData(IoBuffer data, int timestamp, boolean amf);
 
     /**
      * Returns information used to configure the decoder.
      *
      * @return the data for decoder setup.
      */
-    public IoBuffer getDecoderConfiguration();
+    default IoBuffer getDecoderConfiguration() {
+        return null;
+    }
 
 }
diff --git a/io/src/main/java/org/red5/codec/IVideoStreamCodec.java b/io/src/main/java/org/red5/codec/IVideoStreamCodec.java
index a0eb9f396257c9e5d0b07f4bd35f92b318e093e8..e7d7c97cd1f288aaf1ce95ba044b40f7deb596a1 100644
--- a/io/src/main/java/org/red5/codec/IVideoStreamCodec.java
+++ b/io/src/main/java/org/red5/codec/IVideoStreamCodec.java
@@ -19,22 +19,27 @@ public interface IVideoStreamCodec {
      */
     static final byte FLV_FRAME_KEY = 0x10;
 
+    /**
+     * @return the codec type.
+     */
+    VideoCodec getCodec();
+
     /**
      * @return the name of the video codec.
      */
-    public String getName();
+    String getName();
 
     /**
      * Reset the codec to its initial state.
      */
-    public void reset();
+    void reset();
 
     /**
      * Check if the codec supports frame dropping.
      *
      * @return if the codec supports frame dropping.
      */
-    public boolean canDropFrames();
+    boolean canDropFrames();
 
     /**
      * Returns true if the codec knows how to handle the passed stream data.
@@ -43,7 +48,7 @@ public interface IVideoStreamCodec {
      *            some sample data to see if this codec can handle it
      * @return can this code handle the data.
      */
-    public boolean canHandleData(IoBuffer data);
+    boolean canHandleData(IoBuffer data);
 
     /**
      * Update the state of the codec with the passed data.
@@ -52,7 +57,7 @@ public interface IVideoStreamCodec {
      *            data to tell the codec we're adding
      * @return true for success. false for error
      */
-    public boolean addData(IoBuffer data);
+    boolean addData(IoBuffer data);
 
     /**
      * Update the state of the codec with the passed data.
@@ -62,7 +67,7 @@ public interface IVideoStreamCodec {
      * @param timestamp time associated with the data
      * @return true for success. false for error
      */
-    public boolean addData(IoBuffer data, int timestamp);
+    boolean addData(IoBuffer data, int timestamp);
 
     /**
      * Add video data with a time stamp and a flag identifying the content as AMF or not.
@@ -72,35 +77,37 @@ public interface IVideoStreamCodec {
      * @param amf if true, data is in AMF format otherwise its most likely from non-AMF source like RTP
      * @return true if data is added and false otherwise
      */
-    public boolean addData(IoBuffer data, int timestamp, boolean amf);
+    boolean addData(IoBuffer data, int timestamp, boolean amf);
 
     /**
      * Returns keyframe data.
      *
      * @return the data for a keyframe
      */
-    public IoBuffer getKeyframe();
+    IoBuffer getKeyframe();
 
     /**
      * Returns all the keyframe data.
      *
      * @return array of keyframe data
      */
-    public FrameData[] getKeyframes();
+    FrameData[] getKeyframes();
 
     /**
      * Returns information used to configure the decoder.
      *
      * @return the data for decoder setup
      */
-    public IoBuffer getDecoderConfiguration();
+    default IoBuffer getDecoderConfiguration() {
+        return null;
+    }
 
     /**
      * Returns the number of interframes collected from last keyframe.
      *
      * @return number of interframes
      */
-    public int getNumInterframes();
+    int getNumInterframes();
 
     /**
      * Gets data of interframe with the specified index.
@@ -109,7 +116,7 @@ public interface IVideoStreamCodec {
      *            of interframe
      * @return data of the interframe or null if index is not valid
      */
-    public FrameData getInterframe(int index);
+    FrameData getInterframe(int index);
 
     /**
      * Holder for video frame data.
diff --git a/io/src/main/java/org/red5/codec/MP3Audio.java b/io/src/main/java/org/red5/codec/MP3Audio.java
index 7704ffcd2dc68732b0fca28ca8c94bded1eeaa99..f1af9cae4ec3ed3e02d7a33e613a51bbcf286fbe 100644
--- a/io/src/main/java/org/red5/codec/MP3Audio.java
+++ b/io/src/main/java/org/red5/codec/MP3Audio.java
@@ -17,11 +17,13 @@ import org.apache.mina.core.buffer.IoBuffer;
  */
 public class MP3Audio extends AbstractAudio {
 
-    static final String CODEC_NAME = "MP3";
+    {
+        codec = AudioCodec.MP3;
+    }
 
     @Override
     public String getName() {
-        return CODEC_NAME;
+        return codec.name();
     }
 
     @Override
diff --git a/io/src/main/java/org/red5/codec/OpusAudio.java b/io/src/main/java/org/red5/codec/OpusAudio.java
index c6c8aea2ea9f63b0f7414e66c88bb69f26d860a4..46a982c3adc0013cfd2611f27de118aa5cfe85ee 100644
--- a/io/src/main/java/org/red5/codec/OpusAudio.java
+++ b/io/src/main/java/org/red5/codec/OpusAudio.java
@@ -43,6 +43,10 @@ public class OpusAudio extends AbstractAudio {
     // 48k stereo is the default configuration.
     private volatile boolean needConfig;
 
+    {
+        codec = AudioCodec.OPUS;
+    }
+
     @Override
     public String getName() {
         return CODEC_NAME;
diff --git a/io/src/main/java/org/red5/codec/SpeexAudio.java b/io/src/main/java/org/red5/codec/SpeexAudio.java
index 1bc77f040d4f144e714b3ffb6b5643f096a24d9c..26d54bc2449e618959da9b072bf53b41a2581894 100644
--- a/io/src/main/java/org/red5/codec/SpeexAudio.java
+++ b/io/src/main/java/org/red5/codec/SpeexAudio.java
@@ -19,6 +19,10 @@ public class SpeexAudio extends AbstractAudio {
 
     static final String CODEC_NAME = "Speex";
 
+    {
+        codec = AudioCodec.SPEEX;
+    }
+
     @Override
     public String getName() {
         return CODEC_NAME;
diff --git a/io/src/main/java/org/red5/codec/ULAWAudio.java b/io/src/main/java/org/red5/codec/ULAWAudio.java
index 447d433493ce1c0f0b7c097c59866ce1308fa842..11e164c4365daf4767782f8390595cb10d7b6eab 100644
--- a/io/src/main/java/org/red5/codec/ULAWAudio.java
+++ b/io/src/main/java/org/red5/codec/ULAWAudio.java
@@ -19,6 +19,10 @@ public class ULAWAudio extends AbstractAudio {
 
     static final String CODEC_NAME = "PCM uLaw";
 
+    {
+        codec = AudioCodec.PCM_MULAW;
+    }
+
     @Override
     public String getName() {
         return CODEC_NAME;
diff --git a/pom.xml b/pom.xml
index d70d054201ac20ac8af5498a71f10b5c5b0f25d1..ad51276ad2646833c2e8364e4bb9a30988978d18 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
     <name>Red5</name>
     <description>The Red5 server</description>
     <groupId>org.red5</groupId>
-    <version>1.3.29</version>
+    <version>1.3.30</version>
     <url>https://github.com/Red5/red5-server</url>
     <inceptionYear>2005</inceptionYear>
     <organization>
@@ -105,7 +105,7 @@
         <bc.version>1.62</bc.version>
         <mina.version>2.0.23</mina.version>
         <!-- Can no longer open-end spring, 6.0 forces jdk 17 -->
-        <spring.version>5.3.31</spring.version>
+        <spring.version>5.3.32</spring.version>
         <tomcat.version>8.5.95</tomcat.version>
         <junit.version>[4.13.1,)</junit.version>
         <isoparser.version>1.9.39</isoparser.version>
diff --git a/server/pom.xml b/server/pom.xml
index b7fb4bd4579b13faf0de7ade941a2ff31305c67a..77edd3e8d224127e7ec7b5421fafa8227b172300 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <groupId>org.red5</groupId>
         <artifactId>red5-parent</artifactId>
-        <version>1.3.29</version>
+        <version>1.3.30</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>red5-server</artifactId>
diff --git a/server/src/main/java/org/red5/server/plugin/PluginLauncher.java b/server/src/main/java/org/red5/server/plugin/PluginLauncher.java
index 9f548ad3a81cb46a7b1c8f8eeddf406067adf386..74687249fea7d729716780b870105c2fe5970227 100644
--- a/server/src/main/java/org/red5/server/plugin/PluginLauncher.java
+++ b/server/src/main/java/org/red5/server/plugin/PluginLauncher.java
@@ -88,6 +88,7 @@ public class PluginLauncher implements ApplicationContextAware, InitializingBean
                 try {
                     pluginClass = Class.forName(pluginMainClass, true, loader);
                 } catch (ClassNotFoundException e) {
+                    log.warn("Error loading plugin class: {}", pluginMainClass, e);
                     continue;
                 }
                 try {
diff --git a/server/src/main/server/conf/jee-container.xml b/server/src/main/server/conf/jee-container.xml
index a676b946c7126412d65a2fce50ff224a54f36b7c..63b27613ae9b2fb20ba23400809a1df53bbf7621 100644
--- a/server/src/main/server/conf/jee-container.xml
+++ b/server/src/main/server/conf/jee-container.xml
@@ -21,7 +21,7 @@
             <property name="protocol" value="org.apache.coyote.http11.Http11Nio2Protocol" />
      -->
     <!-- Tomcat without SSL enabled -->
-    <bean id="tomcat.server" class="org.red5.server.tomcat.TomcatLoader" depends-on="context.loader,warDeployer" lazy-init="true">
+    <bean id="tomcat.server" class="org.red5.server.tomcat.TomcatLoader" depends-on="context.loader" lazy-init="true">
         <property name="websocketEnabled" value="true" />
         <property name="webappFolder" value="${red5.root}/webapps" />
         <property name="connectors">
diff --git a/service/pom.xml b/service/pom.xml
index 523f848338424c923844f81fd2b09ce5ae8f6a81..9cbef4099db8f56a9ecd94b1ddf20a263142dc50 100644
--- a/service/pom.xml
+++ b/service/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <groupId>org.red5</groupId>
         <artifactId>red5-parent</artifactId>
-        <version>1.3.29</version>
+        <version>1.3.30</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>red5-service</artifactId>
diff --git a/servlet/pom.xml b/servlet/pom.xml
index 4167e38f78b2b26ec44490dc63be437ca1d2f988..d6868eda33591263e9dbf6113b482fb671a764db 100644
--- a/servlet/pom.xml
+++ b/servlet/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <groupId>org.red5</groupId>
         <artifactId>red5-parent</artifactId>
-        <version>1.3.29</version>
+        <version>1.3.30</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>red5-servlet</artifactId>