Search code examples
javawowza

wowza java api inject captioning


We have a bunch of .mp4 files along with their associated .ttml open caption files. We are using Wowza to dynamically load and stream these files based on a dynamic schedule without interrupting the stream.

I was able to follow the tutorial here ( https://www.wowza.com/docs/how-to-use-ipublishingprovider-api-to-publish-server-side-live-streams ) to stream individual mp4 files at specified times.

However, I could not find a Java API command to inject an associated caption files. I found this page ( https://www.wowza.com/docs/how-to-configure-closed-captioning-for-live-streaming ), which doesn't talk about Java API but rather a config setting. At the end of the page was a sample package that shows some API calls, but that section was described as "legacy". I could extend that, but since it's "legacy", I fear that whatever API the module depends on may be unavailable with later versions of WMS.

Is there a low-level way (beside the "legacy" way) to tell the system to load a ttml file (or just manually inject text) immediately following a PublishingProviderMediaReader call?

Below is the code pasted from the link above

package com.wowza.wms.plugin.test.integration;

import com.wowza.wms.stream.publish.*;
import com.wowza.wms.vhost.*;
import com.wowza.wms.logging.*;

public class ServerPublisherWorker extends Thread
{
    private long sleepTime = 75;
    private boolean running = true;
    private Object lock = new Object();

    private String applicationName = "live";
    private String vodStreamName = "mp4:sample.mp4";
    private String liveStreamName = "myStream";
    private String publishStreamName = "publishstream";
    private int cycleTime = 10000;

    public synchronized void quit()
    {
        synchronized(lock)
        {
            running = false;
        }
    }

    public void run()
    {
        WMSLoggerFactory.getLogger(ServerPublisherWorker.class).info("ServerPublisherWorker.run: START");

        long startTime = System.currentTimeMillis();
        long playStartTime = startTime;

        try
        {
            IVHost vhost = VHostSingleton.getInstance(VHost.VHOST_DEFAULT);
            Publisher publisher = Publisher.createInstance(vhost, applicationName);

            publisher.publish(publishStreamName);

            long nextSwitch = playStartTime + cycleTime;
            long nextType = 0;
            IPublishingProvider provider = new PublishingProviderMediaReader(publisher, playStartTime, vodStreamName);
            //provider.seek(20000);
            provider.setRealTimeStartTime(startTime);

            WMSLoggerFactory.getLogger(ServerPublisherWorker.class).info("ServerPublisherWorker.run: Start with vod stream: "+vodStreamName);

            while(true)
            {
                boolean moreInFile = provider!=null?provider.play(publisher):false;

                long currentTime = System.currentTimeMillis();
                if (!moreInFile || currentTime > nextSwitch)
                {
                    if (provider != null)
                        provider.close();
                    provider = null;

                    if ((nextType % 2) == 0)
                    {
                        provider = new PublishingProviderLive(publisher, publisher.getMaxTimecode(), liveStreamName);
                        //((PublishingProviderLive)provider).setStartOnPreviousKeyFrame(false);
                        provider.setRealTimeStartTime(currentTime);

                        WMSLoggerFactory.getLogger(ServerPublisherWorker.class).info("ServerPublisherWorker.run: Switch to live stream: "+liveStreamName);
                    }
                    else
                    {
                        provider = new PublishingProviderMediaReader(publisher, publisher.getMaxTimecode(), vodStreamName);
                        //provider.seek(20000);
                        provider.setRealTimeStartTime(currentTime);

                        WMSLoggerFactory.getLogger(ServerPublisherWorker.class).info("ServerPublisherWorker.run: Switch to vod stream: "+vodStreamName);
                    }

                    nextSwitch = currentTime + cycleTime;
                    nextType++;

                    if (nextType == 100)
                        break;
                }
                else
                    sleep(sleepTime);

                synchronized(lock)
                {
                    if (!running)
                        break;
                }
            }

            provider.close();

            publisher.publish(null);

            synchronized(lock)
            {
                running = false;
            }
        }
        catch (Exception e)
        {
            WMSLoggerFactory.getLogger(ServerPublisherWorker.class).error("ServerPublisherWorker.run: "+e.toString());
            e.printStackTrace();
        }

        WMSLoggerFactory.getLogger(ServerPublisherWorker.class).info("ServerPublisherWorker.run: STOP");
    }
}

And the base listener that calls the above code:

package com.wowza.wms.plugin.test.integration;

import com.wowza.wms.server.*;

public class ServerPublisherServerListener implements IServerNotify
{
    ServerPublisherWorker worker = null;

    public void onServerCreate(IServer server)
    {
    }

    public void onServerInit(IServer server)
    {
        worker = new ServerPublisherWorker();
        worker.start();
    }

    public void onServerShutdownComplete(IServer server)
    {
    }

    public void onServerShutdownStart(IServer server)
    {
        if (worker != null)
            worker.quit();
        worker = null;
    }

}

Any assistance or input is much appreciated. Thank you in advance.


Solution

  • You can inject cuepoints or metadata from your stream by using the following:

    package com.wowza.example.module;
    
    import com.wowza.wms.amf.*;
    import com.wowza.wms.client.*;
    import com.wowza.wms.module.*;
    import com.wowza.wms.request.*;
    import com.wowza.wms.stream.*;
    
    public class ModuleInjectData extends ModuleBase {
    
        public void setCaption (IClient client, RequestFunction function, AMFDataList params)
        {
            String streamname = params.getString(PARAM1);
            String text =  params.getString(PARAM2);
            String language = params.getString(PARAM3);
            String trackid = params.getString(PARAM4);
    
            IMediaStream stream = client.getAppInstance().getStreams().getStream(streamname);
    
            //essential code
            AMFDataMixedArray data = new AMFDataMixedArray();
            data.put("text", new AMFDataItem(text));
            data.put("language", new AMFDataItem(language));
            data.put("trackid", new AMFDataItem(trackid));
            stream.sendDirect("onTextData", data);
            ((MediaStream)stream).processSendDirectMessages();
            getLogger().info("Caption: " + text);
        }
    
        public void injectMetaData(IClient client, RequestFunction function, AMFDataList params)
        {
            String streamName = params.getString(PARAM1);
            String data =  params.getString(PARAM2);
            IMediaStream stream = client.getAppInstance().getStreams().getStream(streamName);
            if (stream != null)
            {
                AMFDataList amfList = new AMFDataList();
    
                amfList.add(new AMFDataItem("@setDataFrame"));
                amfList.add(new AMFDataItem("onMetaData"));
    
                AMFDataMixedArray metaData = new AMFDataMixedArray();
    
                metaData.put("param1", data);
                metaData.put("param2", new AMFDataItem("data2"));
    
                amfList.add(metaData);
    
                synchronized(stream)
                {
                    byte[] dataData = amfList.serialize();
                    int size = dataData.length;
                    long timecode = Math.max(stream.getAudioTC(), stream.getVideoTC());
                    stream.setDataTC(timecode);
                    stream.setDataSize(size);
                    stream.startDataPacket();
                    stream.addDataData(dataData, 0, size);
                }
            }
        }
    }
    

    You will need to create a BufferedReader object to read your file and inject the data using the above methods.