I would like to talk about the design considerations we made when we started working on the 3rd version of XJS Framework, but first, a bit of a history.

I was one of the pioneers of the framework when we envisioned a library that would encapsulate all the core functionalities of XSplit Broadcaster exposed to JavaScript, reducing boilerplate by abstracting most of the common functionalities.

The first version was more of a 1-to-1 mapping of features, wherein we were just adding automatic attachment of items.

Version 2, which is the official public version as of now, took it even further. Almost everything was abstracted, which we thought would increase developer productivity by removing the need for them to worry about the lower level behavior of the software. We somewhat achieved our objectives, wherein while working on most of the plugins in XSplit Broadcaster, we did not have to bother thinking how to list items in a scene, or what external calls should be called first before getting or modifying a property of an item.

All looks good ei?

The Problem

One of the pain points of XJS 2.x surfaced when new features were added to XSplit Broadcaster. There were a couple of plugin developers wanting to modify a particular property, but couldn't because the framework did not yet support (read: abstract) it.

Another issue was the total file size of XJS... To simply put, it's very large. Although most of the time 3rd party XSplit Broadcaster plugins are hosted locally, there are times that it makes sense to host it over the internet. Load times could be a bane in this scenario.

3.0

After leaving SplitmediaLabs, I continued to work with them as a part-time consultant. One of the projects that I got was to continue working on XJS Framework, improving the framework based on the feedback and pain points that we had when using the framework. It's been quite some time since I last maintained XJS, which gave me some fresh outlook on what needs to be done.

So the objectives were:

  • Extensibility. Plugin developers does not need to wait for a new XJS Framework version to use the latest and greatest features supported in the latest XSplit Broadcaster version.
  • Minimal Footprint. Decrease the total file size of the framework. Since it's almost impossible to have a lodash-esque "import specific module" approach (ie. import get from 'lodash/get';) due to how the data flows between JavaScript and the XSplit core, we have to minimize the abstraction in the core functionalities. We also opted to separate the list of properties so that plugin developers would only import the properties if they need them... since we anticipated that the properties list would end up being a large file.
  • Multiple Instances. Another feature request was a way to use XJS to remotely control another XSplit Broadcaster instance over the network. We thought that the plugin might also need to control its own instance (the XSplit Broadcaster instance where the plugin is running on), but that isn't possible in the current 2.x version since it's a singleton and only allows 1 instance to run at a time.

I'm glad to say that we mostly achieved the objectives, albeit it is still in very early stages. As of now, I am not focusing on the framework alone as I've got other projects, but I'm adding onto it while using it for another upcoming XSplit Broadcaster plugin.

I'd like to discuss how we achieved the objectives in this blog post.

Extensibility

Unlike the previous version of XJS, we opted not to abstract all the properties into the framework. This means that you won't be able to do this:

const pluginName = await item.getName();

We instead opted to just create some helper methods that would consume an object that defines a property. The object that it consumes would be the one responsible to identify what the underlying property key is, what it should do with the response from the core, transform the parameters passed into one that the core would understand, and do the validation.

Now that's a lot to ask for the plugin developer... So we decided to also provide a file with most of the properties already included, so that plugin developers would not need to bother reading through lower level documentation.

Knowing how to create a property object would only be very useful for plugin developers if incase that there is a new feature released, and they would want to access that feature even before the XJS Framework team gets to include it in the official properties file.

Here's a typescript interface that should give you an idea on the structure of a property:

interface AppPropsType {
	key: string;
	setValidator: (param: any) => boolean;
	setTransformer: (param: any) => string;
	getValidator: (param: any) => boolean;
	getTransformer: (param: string) => any;
}

I'd like to expound this a bit, but I already made a notion page that should give you more information about this: https://www.notion.so/dcefram/Application-and-Item-Properties-fde4bb0250d94ff39c8be84ee6b865e2

Also, here's the temporary documentation for item properties and application properties.

Minimal Footprint

Continuing with the topic of properties, the official properties file isn't bundled with the main xjs-framework module/package. Although it gets installed to your node_modules, importing xjs-framework on your plugin would not automatically import the properties file. You would need to manually import it. This ensures that your plugin/package won't get bloated with code that you do not use.

Here's an example on how you would get a property:

import Xjs from 'xjs-framework';
import ItemProps from 'xjs-framework/props/item-props';

async function main() {
    const xjs = new Xjs();
    const view = await xjs.getView(0);
    const scene = await view.getCurrentScene();
    const items = await scene.getItems();

    const cname = await items[0].getProperty(ItemProps.customName);
    console.log(cname);
}

main();

If your plugin does not need to access item properties, then you would not need to import xjs-framework/props/item-props. That should shave off a couple of KBs from your final package/bundle.

Multiple instances

2.x is a singleton, and although it was generally a great idea, we had to ditch that due to the need to support multiple instances of XJS. The value on this decision is when you are implementing a XSplit Broadcaster plugin that can control another XSplit Broadcaster instance over the network, while still being able to control its own local instance.

The application of this concept are many. You could create a production-level setup, wherein one main machine can control multiple other machines running XSplit Broadcaster, maybe the main machine is streaming to a projector, while other machines are streaming to different stream channels, possibly a channel per language.

In order to ensure that each XJS instance would be able to receive their callbacks from the correct source, we had to have each XJS instance have their own instance of an internal class that would listen to the correct source. We also had to ensure that each internal class that listens to the local instance would have to pass the callbacks to other instances... This is a product of how the core system is designed, wherein it only expects one listener in the frontend. We had to workaround that limitation and just pass over the values that the core returned to the frontend to all of the XJS instances.

Conclusion

Overall, the project seemed to be in the correct trajectory in my eyes, although it is still lacking 3rd party feedback. I will continue to use this on the plugins that I am working on, while adding new features as needed while considering the 3 objectives of 3.0.

There are still some tooling work to be done, and I'd like to get to work on it on my extra time :)