Playing Video in a Single-Sourced RAP/RCP Application

October 12, 2012 | 5 min Read

This post outlines the challenges we encountered when implementing video playback in a single-sourced [1] Eclipse RCP/ RAP [2] application.

Our team at EclipseSource is developing medical software which runs both as a web application and an Eclipse RCP application. Hospitals are using this tool as part of the patient’s briefing. During the briefing, the patient is informed about the medical treatments he is going to receive and is also asked to fill out a questionnaire. An essential part of the briefing is to show the patient a few video clips.

Figure 1 depicts the major components related to the video delivery. Thanks to the versatile SWT Browser widget, the same video playback technology can be used for both the RCP and RAP versions. For this demo we will use Video-JS player [3], which supports both native HTML5 video and Adobe Flash fallback. Each video clip is available in Webm and MP4 formats. The Video-JS player offers both formats to the HTML5 capable browsers and loads MP4 in a flash video player for the older browsers. The video files are delivered via the OSGi HTTP service.

Figure 1. Application architecture.

The video is shown in a pop-up dialog, which contains the Browser widget. Depending on the video assigned to a particular patient, a helper class – VideoHelper, prepares the HTML for the browser. The preparation consists of customizing an HTML template with the particular video file names and URLs.

To construct the correct URLs, we need to know a few details of the HTTP service. In particular, we have to figure out which port the HTTP service is bound to, and, in the case of the RAP application, the context path. Obtaining this information is different for RCP and RAP, so we will have to apply single-sourcing. The class HostInfo (see figure 3) has two implementations, which are located in platform-specific fragments.

public abstract class HostInfo  {
    private static HostInfo _instance;
    public static HostInfo getInstance() {
        if (_instance == null) {
            _instance = ImplementationLoader.newInstance(HostInfo.class);
        }
        return _instance;
    }
    public void initialize() {
        init();
    }
    public String getHost() {
        return doGetHost();
    }
    public int getPort() {
        return doGetPort();
    }
    public String getContext() {
        return doGetContext();
    }
    protected abstract void init();
    protected abstract String doGetScheme();
    protected abstract String doGetHost();
    protected abstract int doGetPort();
    protected abstract String doGetContext();
    protected abstract void doSetScheme(final String scheme);
    protected abstract void doSetHost(final String host);
    protected abstract void doSetPort(final int port);
    protected abstract void doSetContext(final String context);
}

Figure 3. HostInfo.java

The RAP HostInfoImpl uses the RWT singleton to get the current HttpServletRequest and from there to extract the required details (figure 4). The RCP implementation relies on a ServiceTracker, registered in the bundle activator, to obtain the port number of the HTTP service (figure 5).

public class HostInfoImpl extends HostInfo {
 private String host;
 private int port;
 private String context;
 private String scheme;

 private HostInfoImpl getSessionSingletonInstance() {
  return SessionSingleton.getInstance(HostInfoImpl.class);
 }
 @Override
 protected String doGetHost() {
  String host = getSessionSingletonInstance().host;
  if (host == null) {
   throw new IllegalStateException("HostInfo is not initialized.");
  }
  return host;
 }
 @Override
 protected int doGetPort() {
  return getSessionSingletonInstance().port;
 }
 @Override
 protected void doSetHost(final String host) {
  getSessionSingletonInstance().host = host;
 }
 ...
 @Override
 protected void init() {
  doSetHost(RWT.getRequest().getServerName());
  doSetPort(RWT.getRequest().getLocalPort());
  doSetContext(RWT.getRequest().getContextPath());
  doSetScheme(RWT.getRequest().getScheme());
 }
}

Figure 4. HostInfoImpl - RAP version (some getters/setters were deleted for brevity).

public class HostInfoImpl extends HostInfo {
    private String scheme = "http"; //$NON-NLS-1$
    private String host = "localhost"; //$NON-NLS-1$
    private String context = ""; //$NON-NLS-1$

    @Override
    protected String doGetHost() {
        return host;
    }
    @Override
    protected int doGetPort() {
        int port = Activator.getDefault().getServerPort();
        return port;
    }
    @Override
    protected void doSetHost(final String host) {
        this.host = host;
    }
    @Override
    protected void doSetPort(final int port) {
        // do nothing
    }
  ...
}

public class PortTrackerCustomizer implements ServiceTrackerCustomizer {
    private Integer httpPort = null;
...
    public Object addingService(final ServiceReference reference) {
        extractPort(reference);
        return context.getService(reference);
    }
    public void modifiedService(final ServiceReference reference, final Object service) {
        extractPort(reference);
    }
    private void extractPort(final ServiceReference reference) {
        // ignore if the help is starting
        if ("org.eclipse.help".equals(reference.getProperty("other.info"))) {return;} 
        try {
            // The http.port property is not defined if the ServletBridge is
            // used to load Equinox.
            Object httpPortObject = reference.getProperty("http.port"); 
            httpPort = Integer.parseInt(httpPortObject.toString());
        } catch (Exception ex) {
            // Do not throw exception - it will prevent the application from
            // starting when deployed with the ServletBridge.
            httpPort = null;
        }

    }
    public int getHttpPort() {
        if (httpPort == null) {
            throw new IllegalStateException("No OSGi HTTP Service running"); 
        }
        return httpPort;
    }
}

public class Activator extends AbstractUIPlugin {
...
 public void start(BundleContext context) throws Exception {
...
        portTrackerCustomizer = new PortTrackerCustomizer(context);
        portTracker = new ServiceTracker(context, HttpService.class.getName(), portTrackerCustomizer);
        portTracker.open();
 }
...
    public int getServerPort() {
        return portTrackerCustomizer.getHttpPort();
    }
}

Figure 5. HostInfoImpl and the relevant parts from the ServiceTrackrCustomizer and the bundle activator - RCP version.

In the RCP case, there is one more catch worth mentioning – the embedded Jetty server binds by default to port 80. If another process is already bound to this port, then our application will not be able to deliver videos. A simple solution is to provide a JettyCustomizer implementation, which selects a free port from the pool of ephemeral TCP ports. This customizer is in a separate fragment, with org.eclipse.equinox.http.jetty as a host plugin (figure 6).

public class DemoJettyCustomizer extends JettyCustomizer {

    @SuppressWarnings({ "rawtypes" })
    @Override
    public Object customizeHttpConnector(final Object connector, final Dictionary settings) {
        if (connector instanceof Connector) {
            Connector jettyConnector = (Connector)connector;
            jettyConnector.setPort(findFreePort());
        }
        return super.customizeHttpConnector(connector, settings);
    }

    private int findFreePort() {
        ServerSocket socket = null;
        try {
            socket = new ServerSocket(0);
            socket.setReuseAddress(true);
            return socket.getLocalPort();
        } catch (IOException e) {
            // ignore
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                   // ignore
                }
            }
        }
        return 0;
    }
}

Figure 6. Finding a free TCP port - JettyCustomizer.

This demo application is available on GitHub - https://github.com/vtchoumatchenko/rap_rcp_video_demo.git

The repository contains a short trailer from the Big Buck Bunny video project [4].

Feedback on the code is welcome!

References

  1. Single sourcing - /downloads/webcasts/
  2. Eclipse RAP - https://www.eclipse.dev/rap/
  3. HTML5 Video Player - https://videojs.com/
  4. Big Buck Bunny video project - https://www.bigbuckbunny.org/