text:
Learning OpenTok Android Sample App
===================================

This sample app shows how to accomplish basic tasks using the [OpenTok Android SDK] [1].
It connects the user with another client so that they can share an OpenTok audio-video
chat session. The app uses the OpenTok Android SDK to implement the following:

* Connect to an OpenTok session
* Publish an audio-video stream to the session
* Subscribe to another client's audio-video stream
* Record the session, stop the recording, and view the recording
* Implement text chat
* A simple custom audio driver for audio input and output
* A custom video renderer
* A simple custom video capturer
* A custom video capturer that uses the device camera
* Publishing a screen-sharing stream
* A video capturer that lets you obtain still screen captures of the camera used by a publisher

The code for this sample is found the following git branches:

* *basics.step-1* -- This branch shows you how to set up your project to use the OpenTok Android SDK.

* *basics.step-4* -- This branch shows you how to connect to the OpenTok session.

* *basics.step-5* -- This branch shows you how publish a stream to the OpenTok session.

* *basics.step-6* -- This branch shows you how to subscribe to a stream on the OpenTok session.

* *archiving.step-1* -- This branch shows you how to record the session.

* *signaling.step-1* -- This branch shows you how to use the OpenTok signaling API.

* *signaling.step-2* -- This branch shows you how to implement text chat using the OpenTok
signaling API.

* *signaling.step-3* -- This branch adds some UI improvements for the text chat feature.

* *audio-driver.step-1* -- This branch shows you how to implement a custom audio driver that
  uses a simple audio capturer.

* *audio-driver.step-2* -- This branch shows you how to implement a custom audio driver that
  uses a simple audio renderer.

* *basic-renderer* -- This branch shows the basics of implementing a custom video renderer
  for an OpenTok subscriber.

* *basic-capturer* -- This branch shows the basics of implementing a custom video capturer
  for an OpenTok publisher.

* *camera-capturer* -- This branch shows you how to use a custom video capturer using
  the device camera as the video source.

* *screensharing* -- This branch shows you how to use the device's screen (instead of a
  camera) as the video source for a published stream.

## basics.step-1: Starting Point

The basics.step-1 branch includes a basic Android application. Complete the following steps to get
it running in Android Studio (and to add the OpenTok Android SDK):

1. In Android Studio, select the File > Import Project command. Navigate to the root directory of
   this project, select the build.gradle file, and then click the OK button. The project opens in a
   new window.

   The Java code for the application is the ChatActivity class in the
   com.tokbox.android.demo.learningopentok package.

2. Download the [OpenTok Android SDK](https://tokbox.com/opentok/libraries/client/android/).

3. Locate the opentok-android-sdk-2.4.0.jar file in the OpenTok/libs directory of the OpenTok
   Android SDK, and drag it into the app/libs directory of the Android Studio project explorer.

4. If the app/src/main/jniLibs directory does not exist in the Android Studio
   project explorer, right-click the app/src/main directory and select the New Resource Directory
   command, enter jniLibs as the directory name, and then click OK.

6. Locate the armeabi and x86 directories in the OpenTok/libs directory of the OpenTok
   Android SDK, and drag them into the app/src/main/jniLibs directory of the Android Studio
   project explorer.

6. Debug the project on a supported device.

   For a list of supported devices, see the "Developer and client requirements"
   on [this page] [1].

## basics.step-2: Creating a session and defining archive REST API calls (server side)

Before you can test the application, you need to set up a web service to handle some
OpenTok-related API calls. The web service securely creates an OpenTok session.

The [Learning OpenTok PHP](https://github.com/opentok/learning-opentok-php) repo includes code
for setting up a web service that handles the following API calls:

* "/service" -- The Android client calls this endpoint to get an OpenTok session ID, token,
  and API key.

* "/start" -- The Android client calls this endpoint to start recording the OpenTok session to
  an archive.

* "/stop" -- The Android client calls this endpoint to stop recording the archive.

* "/view" -- The Android client load this endpoint in a web browser to display the archive
  recording.

Download the repo and run its code on a PHP-enabled web server,

The HTTP POST request to the /service endpoint returns a response that includes the OpenTok
session ID and token.

## basics.step-3: Generating a token (server side)

The web service also creates a token that the client uses to connect to the OpenTok session.
The HTTP GET request to the /service endpoint returns a response that includes the OpenTok
session ID and token.

You will want to authenticate each user (using your own server-side authentication techniques)
before sending an OpenTok token. Otherwise, malicious users could call your web service and
use tokens, causing streaming minutes to be charged to your OpenTok developer account. Also,
it is a best practice to use an HTTPS URL for the web service that returns an OpenTok token,
so that it cannot be intercepted and misused.

## basics.step-4: Connecting to the session

The code for this section is added in the basics.step-4 branch of the repo.

First, set the app to use the web service described in the previous two sections:

1. Open the WebServiceCoordinator.java file. This is in the com.tokbox.android.demo.learningopentok
   package.

2. Edit the `CHAT_SERVER_URL` and `SESSION_INFO_ENDPOINT` values to match the URL and end-point
   of the web service:

        private static final String CHAT_SERVER_URL = "https://example.com";
        private static final String SESSION_INFO_ENDPOINT = CHAT_SERVER_URL + "/session";

You can now test the app in the debugger. On successfully connecting to the session, the
app logs "Session Connected" to the debug console.

The `onCreate()` method of the main ChatActivity object instantiates a WebServiceCoordinator
object and calls its `fetchSessionConnectionData()` method. This method makes an API call to
the /session endpoint of the web service to obtain the OpenTok API key, session ID, and a token
to connect to the session.

Once the session ID is obtained, the WebServiceCoordinator calls the
`onSessionConnectionDataReady()` method of the ChatActivity object, passing in the OpenTok API key,
session ID, and token. This method sets the properties to these values and then calls the
`initializeSession()` method:

    private void initializeSession() {
      mSession = new Session(this, mApiKey, mSessionId);
      mSession.setSessionListener(this);
      mSession.connect(mToken);
    }

The Session class is defined by the OpenTok Android SDK. It represents the OpenTok session
(which connects users).

This app sets `this` as the listener for Session events, defined by the Session.SessionListener
interface. The ChatActivity class implements this interface, and overrides its methods, such as
the `onConnected(Session session)` method:

    @Override
    public void onConnected(Session session) {
        Log.i(LOG_TAG, "Session Connected");
    }

The Session.SessionListener interface also defined methods for handling other session-related
events, which we will look at in the following sections.

Finally, the `connect(token)` method of the Session object connects the app to the OpenTok session.
You must connect before sending or receiving audio-video streams in the session (or before
interacting with the session in any way).

## basics.step-5: Publishing an audio video stream to the session

The code for this section is added in the basics.step-5 branch of the repo.

First, let's test the code in this branch:

1. Find the test.html file in the root of the project. You will use the test.html file to
   connect to the OpenTok session and view the audio-video stream published by the Android app:

   * Edit the test.html file and set the `sessionCredentialsUrl` variable to match the
     `ksessionCredentialsUrl` property used in the Android app.

   * Add the test.html file to a web server. (You cannot run WebRTC videos in web pages loaded
     from the desktop.)

   * In a browser, load the test.html file from the web server.

2. Run the Android app. The Android app publishes an audio-video stream to the session and the
   the web client app subscribes to the stream.

Now lets look at the Android code. In addition to initializing and connecting to the session, the
`onSessionConnectionDataReady()` method calls the `initializePublisher()` method:

      private void initializePublisher() {
          mPublisher = new Publisher(this);
          mPublisher.setPublisherListener(this);
          mPublisher.setCameraListener(this);
          mPublisher.getRenderer().setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE,
                  BaseVideoRenderer.STYLE_VIDEO_FILL);
          mPublisherViewContainer.addView(mPublisher.getView());
      }

The Publisher object is defined in the OpenTok Android SDK. A Publisher object acquires
an audio and video stream from the device's microphone and camera. These can then be published
to the OpenTok session as an audio-video stream.

The Publisher class is a subclass of the PublisherKit class, also defined in the OpenTok Android
SDK. The PublisherKit class lets you define custom video drivers (capturers and renderers). The
Publisher class uses the device's camera as as the video source, and it implements a pre-built
video capturer and renderer.

The ChatActivity object sets itself to implement the PublisherKit.PublisherListener interface.
As such it implements method of that interface to handle publisher-related events:

      mPublisher.setPublisherListener(this);

The following code sets the Publisher to scale the video to fill the entire area of the
renderer, with cropping as needed:

      mPublisher.getRenderer().setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE,
              BaseVideoRenderer.STYLE_VIDEO_FILL);

The 'getView()` method of the Publisher object returns a View object that displays
a view of the camera. This code displays that view in the app:

      mPublisherViewContainer.addView(mPublisher.getView());

The `mPublisherViewContainer` object is a FrameLayout object set to the `publisher_container`
view defined in the main layout XML file.

Upon successfully connecting to the OpenTok session (see the previous section), the
`onConnected(Session session)` method is called. In this branch of the repo, this method
includes a call to the `publish(publisherKit)` method of the Session object:

    @Override
    public void onConnected(Session session) {
        Log.i(LOG_TAG, "Session Connected");

        if (mPublisher != null) {
            mSession.publish(mPublisher);
        }
    }

The `publish(publisherKit)` method of the Session object publishes an audio-video stream to
the OpenTok session.

Upon successfully publishing the stream, the implementation of the
`onStreamCreated(publisherKit, stream)`  method (defined in the PublisherKit.PublisherListener
interface) is called:

    @Override
    public void onStreamCreated(PublisherKit publisherKit, Stream stream) {
        Log.i(LOG_TAG, "Publisher Stream Created");
    }

If the publisher stops sending its stream to the session, the implementation of the
`onStreamDestroyed(publisherKit, stream)` method is called:

    @Override
    public void onStreamDestroyed(PublisherKit publisherKit, Stream stream) {
        Log.i(LOG_TAG, "Publisher Stream Destroyed");
    }

## basics.step-6: Subscribing to another client's audio-video stream

The code for this section is added in the basics.step-6 branch of the repo.

First, let's test the code in this branch:

1. Find the test.html file in the root of the project. You will use the test.html file to
   connect to the OpenTok session and view the audio-video stream published by the Android app:

   * Edit the test.html file and set the `sessionCredentialsUrl` variable to match the
     `ksessionCredentialsUrl` property used in the Android app.

   * Add the test.html file to a web server. (You cannot run WebRTC videos in web pages loaded
     from the desktop.)

   * In a browser, load the test.html file from the web server.

2. Run the Android app. The Android app subscribes to the audio-video stream published by the
   web page.

The `onStreamReceived(Session session, Stream stream)` method (defined in the
Session.SessionListener interface) is called when a new stream is created in the session.
The app implements this method with the following:

    @Override
    public void onStreamReceived(Session session, Stream stream) {
        Log.i(LOG_TAG, "Stream Received");

        if (mSubscriber == null) {
            mSubscriber = new Subscriber(this, stream);
            mSubscriber.setSubscriberListener(this);
            mSubscriber.getRenderer().setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE,
                    BaseVideoRenderer.STYLE_VIDEO_FILL);
            mSession.subscribe(mSubscriber);
        }
    }

The method is passed a Session and Stream object, which are both defined by the OpenTok Android
SDK. The Stream object represents the stream that another client is publishing. Although this app
assumes that only one other client is connecting to the session and publishing, the method checks
to see if the app is already subscribing to a stream (if the `mSubscriber` property is null).
If not, the method initializes an Subscriber object (`mSubscriber`), used to subscribe to the
stream, passing in the OTStream object to the constructor function. It also sets the ChatActivity
object as the implementor of the SubscriberKit.SubscriberListener interface. This interface defines
methods that handle events related to the subscriber.

The Subscriber class is also defined in the OpenTok Android SDK. It is a subclass of SubscriberKit,
which lets you define a custom video renderer. The Subscriber object implements a built-in video
renderer.

The following code sets the Subscriber to scale the video to fill the entire area of the
renderer, with cropping as needed:

      mSubscriber.getRenderer().setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE,
              BaseVideoRenderer.STYLE_VIDEO_FILL)

The app then calls the `subscribe(SubscriberKit)` method of the Session object to have the app
subscribe to the stream.

When the app starts receiving the subscribed stream, the implementation of the
`onConnected(subscriberKit)` method (defined by the SubscriberKit.SubscriberListener interface)
is called:

    @Override
    public void onConnected(SubscriberKit subscriberKit) {
        Log.i(LOG_TAG, "Subscriber Connected");

        mSubscriberViewContainer.addView(mSubscriber.getView());
    }

It adds view of the subscriber stream (returned by the `getView()` method of the Subscriber object)
as a subview of the `mSubscriberViewContainer` View object.

If the subscriber's stream is dropped from the session (perhaps the client chose to stop publishing
or to disconnect from the session), the implementation of the
`Session.SessionListener.onStreamDropped(session, stream)` method is called:

    @Override
    public void onStreamDropped(Session session, Stream stream) {
        Log.i(LOG_TAG, "Stream Dropped");

        if (mSubscriber != null) {
            mSubscriber = null;
            mSubscriberViewContainer.removeAllViews();
        }
    }

## archiving.step.1

The code for this section is added in the archiving.step-1 branch of the repo. This code builds
upon the code in the basics.step-6 branch of the repo.

The OpenTok archiving API lets you record audio-video streams in a session to MP4 files. You use
server-side code to start and stop archive recordings. In the WebServiceCoordinator file, you set
the following property to the base URL and endpoints of the web service the app calls to start
archive recording, stop recording, and play back the recorded video:

    private static final String CHAT_SERVER_URL = BuildConfig.CHAT_SERVER_URL;
    private static final String SESSION_INFO_ENDPOINT = CHAT_SERVER_URL + "/session";
    private static final String ARCHIVE_START_ENDPOINT = CHAT_SERVER_URL + "/start/:sessionId";
    private static final String ARCHIVE_STOP_ENDPOINT = CHAT_SERVER_URL + "/stop/:archiveId";
    private static final String ARCHIVE_PLAY_ENDPOINT = CHAT_SERVER_URL + "/view/:archiveId"

When the user selects the Start Archive, Stop Archive, and Play Archive menu items from the action
bar or the options menu, the app calls the `startArchive()` and `stopArchive()`, and `playArchive()`
methods. These call web services that call server-side code start and stop archive recordings.
(See [Creating a session and defining archive REST API calls](#1-creating-a-session-and-defining-archive-REST-API-calls).)

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        switch(item.getItemId()) {
            case R.id.action_settings:
                return true;
            case R.id.action_start_archive:
                startArchive();
                return true;
            case R.id.action_stop_archive:
                stopArchive();
                return true;
            case R.id.action_play_archive:
                playArchive();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

Note that the ChatActivity class now implements the Session.ArchiveListener interface. This
means that it implements methods for handling archive-related events.

When archive recording starts, the implementation of the
`onArchiveStarted(Session session, String archiveId, String archiveName)` method (defined
by the Session.ArchiveListener interface) is called:

    @Override
    public void onArchiveStarted(Session session, String archiveId, String archiveName) {
        mCurrentArchiveId = archiveId;
        setStopArchiveEnabled(true);
        mArchivingIndicatorView.setVisibility(View.VISIBLE);
    }

The method stores the archive ID (identifying the archive) to an `mCurrentArchiveId` property.
The method also calls the `setStopArchiveEnabled(true)` method, which causes the Stop Recording
menu item to be displayed. And it causes the `mArchivingIndicatorView` to be displayed (which
displays an archiving indicator image).

When the user selects the Stop Archive command, the app passes the archive ID along to the
web service that stops the archive recording.

When archive recording stops, the implementation of the
`onArchiveStopped(Session session, String archiveId)` method (defined
by the Session.ArchiveListener interface) is called:

    @Override
    public void onArchiveStopped(Session session, String archiveId) {
        mPlayableArchiveId = archiveId;
        mCurrentArchiveId = null;
        setPlayArchiveEnabled(true);
        setStartArchiveEnabled(true);
        mArchivingIndicatorView.setVisibility(View.INVISIBLE);
    }

The method stores the archive ID (identifying the archive) to an `mPlayableArchiveId` property
(and sets `mCurrentArchiveId` to `null`). The method also calls the `setPlayArchiveEnabled(false)`
method, which disables the Play Archive menu item, and it calls `setStartArchiveEnabled(true)` to
enable the Start Archive menu item. And it causes the `mArchivingIndicatorView` to be hidden.

When the user clicks the Play Archive button, the `playArchive()` method
opens a web page (in the device's web browser) that displays the archive recording, by calling
the archive playback REST API, defined in [Creating a session and defining archive REST API calls](#1-creating-a-session-and-defining-archive-REST-API-calls).

## signaling.step-1

The code for this section is added in the signaling.step-1 branch of the repo. This code builds
upon the code in the basics.step-6 branch of the repo.

The OpenTok signaling API lets clients send text messages to other clients connected to the
OpenTok session. You can send a signal message to a specific client, or you can send
a message to every client connected to the session.

In this branch, the following code is added to the `initializeSession()` method:

    mSession.setSignalListener(this);

This sets the ChatActivity object as the implementor of the SubscriberKit.SignalListener interface. This interface defines the `onSignalReceived(session, type, data, connection)` methods. This method
is called when the client receives a signal from the session:

    @Override
    public void onSignalReceived(Session session, String type, String data, Connection connection) {
        Toast toast = Toast.makeText(this, data, Toast.LENGTH_LONG);
        toast.show();
    }

This app uses an android.widget.Toast object to display received signals.

In the `onConnected(session)` method, the following code sends a signal when the app connects to the
session:

    mSession.sendSignal("", "Hello, Signaling!");

This signal is sent to all clients connected to the session. The method has two parameters:

* `type` (String) -- An optional parameter that can be used as a filter for types of signals.

* `data` (String) -- The data to send with the signal.

## signaling.step-2

The code for this section is added in the signaling.step-2 branch of the repo.

In this branch, the following code is added to the `initializeSession()` method:

    mSendButton = (Button)findViewById(R.id.send_button);
    mMessageEditText = (EditText)findViewById(R.id.message_edit_text);

    // Attach handlers to UI
    mSendButton.setOnClickListener(this);

The main layout XML file adds a Button and and EditText element to the main view. This code adds
properties to reference these objects. It also sets the ChatActivity object as the implementor of
the View.OnClickListener interface. This interface defines the `onClick(View v)` method.

In the `onConnected(Session session)` method (called when the app connects to the OpenTok session)
the following line of code is added in this branch:

    enableMessageViews();

The `enableMessageViews()` method enables the Message text field and the Send button:

    private void enableMessageViews() {
        mMessageEditText.setEnabled(true);
        mSendButton.setEnabled(true);
    }

The `onClick(View v)` method is called when the clicks the Send button:

    @Override
    public void onClick(View v) {
        if (v.equals(mSendButton)) {
            sendMessage();
        }
    }

The `sendMessage()` method sends the text chat message (defined in the Message text field)
to the OpenTok session:

    private void sendMessage() {
        disableMessageViews();
        mSession.sendSignal(SIGNAL_TYPE_MESSAGE, mMessageEditText.getText().toString());
        mMessageEditText.setText("");
        enableMessageViews();
    }

Note that in this branch, the `type` of the signal is set to `SIGNAL_TYPE_MESSAGE`
(a string defined as "message"). The `onSignalReceived()` method checks to see if the
signal received is of this type:

    @Override
    public void onSignalReceived(Session session, String type, String data, Connection connection) {
        switch (type) {
            case SIGNAL_TYPE_MESSAGE:
                showMessage(data);
                break;
        }
    }

## signaling.step-3

The code for this section is added in the signaling.step-3 branch of the repo.

First, let's test the code in this branch:

1. Find the test.html file in the root of the project. You will use the test.html file to
   connect to the OpenTok session and view the audio-video stream published by the Android app:

   * Edit the test.html file and set the `sessionCredentialsUrl` variable to match the
     `ksessionCredentialsUrl` property used in the Android app.

   * Add the test.html file to a web server. (You cannot run WebRTC videos in web pages loaded
     from the desktop.)

   * In a browser, load the test.html file from the web server.

2. Run the Android app. Enter some chat message in the Message text field, then click the Send
   button. The web page displays the text message sent by the Android app. You can also send a
   message from the web page to the Android app.

Instead of using a Toast object to display received signals, the code in this branch uses an
android.widget.ListView object. This lets the app display more than one message at a time.
This branch adds the following code to the `onCreate()` method:

    mMessageHistoryListView = (ListView)findViewById(R.id.message_history_list_view);

    // Attach data source to message history
    mMessageHistory = new ChatMessageAdapter(this);
    mMessageHistoryListView.setAdapter(mMessageHistory);

This branch adds code that differentiates between signals (text chat messages) sent from the local
Android client and those sent from other clients connected to the session. The
`onSignalReceived(session, type, data, connection)` method checks the Connection object for
the received signal with the Connection object returned by `mSession.getConnection()`:

    @Override
    public void onSignalReceived(Session session, String type, String data, Connection connection) {
    boolean remote = !connection.equals(mSession.getConnection());
        switch (type) {
            case SIGNAL_TYPE_MESSAGE:
                showMessage(data);
                showMessage(data, remote);
                break;
        }
    }

The Connection object of the received signal represents the connection to the session for the client
that sent the signal. This will only match the Connection object returned by
`mSession.getConnection()` if the signal was sent by the local client.

The `showMessage(messageData, remote)` method has a new second parameter: remote. This is set
to `true` if the message was sent another client (and `false` if it was sent by the local
Android client):

    private void showMessage(String messageData, boolean remote) {
        ChatMessage message = ChatMessage.fromData(messageData);
        message.setRemote(remote);
        mMessageHistory.add(message);
     }

The `ChatMessage.fromData()` method converts the message data (the data in the received signal)
into a ChatMessage object. The mMessageHistoryListView uses the mMessageHistory object as
the adaptor for the data in the list view. The mMessageHistory property is an
android.widget.ArrayAdapter object. This tutorial focuses on the OpenTok Android SDK API. For more
information on the Android classes used in this text chat implementation, see the docs for the
following:

* [ArrayAdaptor] [2]
* [ListView] [3]

## audio-driver.step-1

To see the code for this sample, switch to the audio-driver.step-1 branch. This branch shows
you how to implement a custom audio driver and use a simple audio capturer for audio used by
the stream published by the app.

The OpenTok Android SDK lets you set up a custom audio driver for publishers and subscribers. You
can use a custom audio driver to customize the audio sent to a publisher's stream. You can also
customize the playback of subscribed streams' audio.

This sample application uses the custom audio driver to publish white noise (a random audio signal)
to its audio stream. It also uses the custom audio driver to capture the audio from subscribed
streams and save it to a file.

### Setting up the audio device and the audio bus

In using a custom audio driver, you define a custom audio driver and an audio bus to be
used by the app.

The BasicAudioDevice class defines a basic audio device interface to be used by the app.
It extends the BaseAudioDevice class, defined by the OpenTok Android SDK. To use a custom
audio driver, call the `AudioDeviceManager.setAudioDevice(device)` method. This sample sets
the audio device to an instance of the BasicAudioDevice class:

    AudioDeviceManager.setAudioDevice(new BasicAudioDevice(this));

Use the AudioSettings class, defined in the OpenTok Android SDK, to define the audio format used
by the custom audio driver. The `BasicAudioDevice()` constructor instantiates two AudioSettings
instances -- one for the custom audio capturer and one for the custom audio renderer. It sets
the sample rate and number of channels for each:

    public BasicAudioDevice(Context context) {
        mContext = context;

        mCaptureSettings = new AudioSettings(SAMPLING_RATE, NUM_CHANNELS_CAPTURING);
        mRendererSettings = new AudioSettings(SAMPLING_RATE, NUM_CHANNELS_RENDERING);

        mCapturerStarted = false;
        mRendererStarted = false;

        mAudioDriverPaused = false;

        mCapturerHandler = new Handler();
    }

The constructor also sets up some local properties that report whether the device is capturing
or rendering. It also sets a Handler instance to process the `mCapturer` Runnable object.

The `BasicAudioDevice getAudioBus()` method gets the AudioBus instance that this audio device uses,
defined by the BasicAudioDevice.AudioBus class. Use the AudioBus instance to send and receive audio
samples to and from a session. The publisher will access the
AudioBus object to obtain the audio samples. And subscribers will send audio samples (from
subscribed streams) to the AudioBus object.

### Capturing audio to be used by a publisher

The `[OTAudioDevice startCapture:]` method is called when the audio device should start capturing
audio to be published. The BasicAudioDevice implementation of this method starts the `mCapturer`
thread to be run in the queue after 1 second:

    public boolean startCapturer() {
        mCapturerStarted = true;
        mCapturerHandler.postDelayed(mCapturer, mCapturerIntervalMillis);
        return true;
    }

The `mCapturer` thread produces a buffer containing samples of random data (white noise). It then
calls the `writeCaptureData(data, numberOfSamples)` method of the AudioBus object, which sends the
samples to the audio bus. The publisher in the application uses the samples sent to the audio bus to
transmit as audio in the published stream. Then if a capture is still in progress (if
the app is publishing), the `mCapturer` thread is run again after another second:

    private Runnable mCapturer = new Runnable() {
        @Override
        public void run() {
            mCapturerBuffer.rewind();

            Random rand = new Random();
            rand.nextBytes(mCapturerBuffer.array());

            getAudioBus().writeCaptureData(mCapturerBuffer, SAMPLING_RATE);

            if(mCapturerStarted && !mAudioDriverPaused) {
                mCapturerHandler.postDelayed(mCapturer, mCapturerIntervalMillis);
            }
        }
    };

See the next step, audio-driver.step-2, to see a simple implementation of a custom
audio renderer.

### Other notes on the audio driver API

The AudioDevice class includes other methods that are implemented by the BasicAudioDevice class.
However, this sample does not do anything interesting in these methods, so they are not included
in this discussion.

## audio-driver.step-2

To see the code for this sample, switch to the audio-driver.step-2 branch. This branch shows
you how to implement simple audio renderer for subscribed streams' audio.

The `BasicAudioDevice()` constructor method sets up a file to save the incoming audio to a file.
This is done simply to illustrate a use of the custom audio driver's audio renderer.
The app requires the following permissions, defined in the AndroidManifest.xml file:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

The `BaseAudioDevice initRenderer()` method is called when the app initializes the audio renderer.
The BasicAudioDevice implementation of this method instantiates a new File object, to which
the the app will write audio data:

     @Override
     public boolean initRenderer() {
        mRendererBuffer = ByteBuffer.allocateDirect(SAMPLING_RATE * 2); // Each sample has 2 bytes
        mRendererFile =
          new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
                   , "output.raw");
        if (!mRendererFile.exists()) {
            try {
                mRendererFile.getParentFile().mkdirs();
                mRendererFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

The `BaseAudioDevice.startRendering()` method is called when the audio device should start rendering
(playing back) audio from subscribed streams. The BasicAudioDevice implementation of this method
starts the `mCapturer` thread to be run in the queue after 1 second:

     @Override
     public boolean startRenderer() {
         mRendererStarted = true;
         mRendererHandler.postDelayed(mRenderer, mRendererIntervalMillis);
         return true;
     }

The `mRenderer` thread gets 1 second worth of audio from the audio bus by calling the
`readRenderData(buffer, numberOfSamples)` method of the AudioBus object. It then writes the audio
data to the file (for sample purposes). And, if the audio device is still being used to render audio
samples, it sets a timer to run the `mRendererHandler` thread again after 0.1 seconds:

    private Handler mRendererHandler;
    private Runnable mRenderer = new Runnable() {
        @Override
        public void run() {
            mRendererBuffer.clear();
            getAudioBus().readRenderData(mRendererBuffer, SAMPLING_RATE);
            try {
                FileOutputStream stream = new FileOutputStream(mRendererFile);
                stream.write(mRendererBuffer.array());
                stream.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (mRendererStarted && !mAudioDriverPaused) {
                mRendererHandler.postDelayed(mRenderer, mRendererIntervalMillis);
            }

        }
    };

This example is intentionally simple for instructional purposes -- it simply writes the audio data
to a file. In a more practical use of a custom audio driver, you could use the custom audio driver
to play back audio to a Bluetooth device or to process audio before playing it back.

# Basic Video Renderer

To see the code for this sample, switch to the basic-renderer branch. This branch shows you
how to make minor modifications to the video renderer used by a Subscriber object. You can also
use the same techniques to modify the video renderer used by a Publisher object (though this
example only illustrates a custom renderer for a subscriber).

In this example, the app uses a custom video renderer to display a black-and-white version of the
Subscriber object's video.

BlackWhiteVideoRender is a custom class that extends the BaseVideoRenderer protocol (defined
in the OpenTok Android SDK). The BaseVideoRenderer class lets you define a custom video renderer
to be used by an OpenTok publisher or subscriber.

    mSubscriberRenderer = new BlackWhiteVideoRender(this);

In the main ChatActivity class, after initializing a Subscriber object, the `setRenderer(renderer)`
method of the Subscriber object is called to set the custom video renderer for the subscriber:

    mSubscriber = new Subscriber(this, stream);
    mSubscriber.setRenderer(mSubscriberRenderer);

The `BlackWhiteVideoRender()` constructor sets a `mRenderView` property to a GLSurfaceView object.
The app uses this object to display the video using OpenGL ES 2.0. The renderer for this
GLSurfaceView object is set to a GLRendererHelper object. GLRendererHelper is a custom class that
extends GLSurfaceView.Renderer, and it is used to render the subscriber video to the GLSurfaceView
object:

    public BlackWhiteVideoRender(Context context) {
        mRenderView = new GLSurfaceView(context);
        mRenderView.setEGLContextClientVersion(2);

        mRenderer = new GLRendererHelper();
        mRenderView.setRenderer(mRenderer);

        mRenderView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }

The GLRendererHelper class includes code that converts a video frame to
a black-and-white representation.

The `BaseVideoRenderer.onFrame()` method is called when the subscriber (or subscriber) renders
a video frame to the video renderer. The frame is an BaseVideoRenderer.Frame object (defined by
the OpenTok Android SDK).  In the BlackWhiteVideoRender implementation of this method, it takes
the frame's image buffer, which is a YUV representation of the frame, and transforms it into
black-and-white. It then passes the buffer to the `displayFrame()` method of the GLRendererHelper
object and calls the `requestRender()` method of the GLSurfaceView object:

    @Override
    public void onFrame(Frame frame) {
        ByteBuffer imageBuffer = frame.getBuffer();

        // Image buffer is represented using three planes, Y, U and V.
        // Data is laid out in a linear way in the imageBuffer variable
        // Y plane is first, and its size is the same of the image (width * height)
        // U and V planes are next, in order to produce a B&W image, we set both
        // planes with the same value.

        int startU = frame.getWidth() * frame.getHeight();
        for (int i = startU; i < imageBuffer.capacity(); i++) {
            imageBuffer.put(i, (byte)-127);
        }

        mRenderer.displayFrame(frame);
        mRenderView.requestRender();
    }

The GLRendererHelper class renders the frame contents to an OpenGL surface in Android.

# Basic Video Capturer

To see the code for this sample, switch to the basic-capturer branch. This branch shows you
how to make minor modifications to the video capturer used by the Publisher class.

In this example, the app uses a custom video capturer to publish random pixels (white noise).
This is done simply to illustrate the basic principals of setting up a custom video capturer.
(For a more practical example, see the Camera Video Capturer and Screen Video Capturer examples,
described in the sections that follow.)

In the `initializePublisher()` method ChatActivity class, after creating a Publisher object,
the code calls the `setCapturer(capturer)` method of the Publisher object, passing in a
NoiseVideoCapturer object:

    mPublisher.setCapturer(new NoiseVideoCapturer(320, 240));

NoiseVideoCapturer is a custom class that extends the BaseVideoCapturer class (defined
in the OpenTok iOS SDK). This class lets you define a custom video capturer to be used
by an OpenTok publisher.

The `BaseVideoCapturer.init()` method initializes capture settings to be used by the custom
video capturer. In this sample's custom implementation of BaseVideoCapturer (NoiseVideoCapturer)
the `initCapture()` method sets properties of a `mCapturerSettings` property:

    @Override
    public void init() {
        mCapturerHasStarted = false;
        mCapturerIsPaused = false;

        mCapturerSettings = new CaptureSettings();
        mCapturerSettings.height = mHeight;
        mCapturerSettings.width = mWidth;
        mCapturerSettings.format = BaseVideoCapturer.ARGB;
        mCapturerSettings.fps = FPS;
        mCapturerSettings.expectedDelay = 0;
    }

The BaseVideoCapturer.CaptureSettings class (which defines this `mCapturerSettings` property)
is defined by the OpenTok Android SDK. In this sample code, the format of the video capturer is
set to use ARGB as the pixel format, with a specific number of frames per second, a specific height,
and a specific width.

The `[OTVideoCapture setVideoCaptureConsumer]` sets an OTVideoCaptureConsumer object (defined
by the OpenTok iOS SDK) the the video consumer uses to transmit video frames to the publisher's
stream. In the OTKBasicVideoCapturer, this method sets a local OTVideoCaptureConsumer instance
as the consumer:

    - (void)setVideoCaptureConsumer:(id<OTVideoCaptureConsumer>)videoCaptureConsumer
    {
        // Save consumer instance in order to use it to send frames to the session
        self.consumer = videoCaptureConsumer;
    }

The `BaseVideoCapturer startCapture()` method is called when a publisher starts capturing video
to send as a stream to the OpenTok session. This will occur after the `Session.publish(publisher)`
method is called. In the NoiseVideoCapturer implementation of this method, the `run()`
`mFrameProducer` thread is started:

    @Override
    public int startCapture() {
        mCapturerHasStarted = true;
        mFrameProducer.run();
        return 0;
    }

The `[self produceFrame]` method creates a buffer of bytes and fills it with random noise.
(Note that each frame is four bytes in the buffer, defining the constituent ARGB values for
the frame.) It then passes the buffer into the
`provideByteArrayFrame(data, format, width, height, rotation, mirrorX)` method, defined by the
BaseVideoCapturer class (in the OpenTok Android SDK):

    Runnable mFrameProducer = new Runnable() {
        @Override
        public void run() {
            Random random = new Random();
            byte[] buffer = new byte[mWidth * mHeight * 4];
            byte[] randoms = new byte[4];
            for (int i = 0; i < mWidth * mHeight * 4; i += 4) {
                random.nextBytes(randoms);
                buffer[i] = randoms[0];
                buffer[i + 1] = randoms[1];
                buffer[i + 2] = randoms[2];
                buffer[i + 3] = randoms[3];
            }

            provideByteArrayFrame(buffer, BaseVideoCapturer.ARGB,
                    mWidth, mHeight, Surface.ROTATION_0, false);

            if (mCapturerHasStarted && !mCapturerIsPaused) {
                mFrameProducerHandler.postDelayed(mFrameProducer, mFrameProducerIntervalMillis);
            }
        }
    };

This causes the publisher to send the frame of data to the video stream in the session.
If the session is still publishing data, the `mFrameProducer` thread is run again
after a specified delay (`mFrameProducerIntervalMillis`), causing another frame of video to be
captured and published.

# Camera video capturer

To see the code for this sample, switch to the camera-capturer branch. This branch shows you
how to use a custom video capturer using the device camera as the video source.

Before studying this sample, see the basic-capturer sample.

In the `initializePublisher()` method of the ChatActivity class, after creating a Publisher object,
the code calls the `setCapturer(capturer)` method of the Publisher object, passing in a
CameraVideoCapturer object:

    mPublisher.setCapturer(new CameraVideoCapturer(this, 640, 480, 30));

CameraVideoCapturer is a custom class that extends the BaseVideoCapturer class (defined
in the OpenTok iOS SDK). This class lets you define a custom video capturer to be used
by an OpenTok publisher. The CameraVideoCapturer class uses a FrontCameraFrameProvider
object to capture frames from the Android camera:

    public CameraVideoCapturer(Context context, int width, int height, int fps) {
        mWidth = width;
        mHeight = height;
        mDesiredFps = fps;

        mHelper = new FrontCameraFrameProvider(context, mWidth, mHeight, mDesiredFps);
        mHelper.setHelperListener(this);
    }

The FrontCameraFrameProvider class uses a FrontCameraFrameProvider.ProviderListener interface
to send events when frames are available for capture. The CameraVideoCapturer class
implements this interface and its `cameraFrameReady()` method.

The `BaseVideoCapturer.init()` method initializes capture settings to be used by the custom
video capturer. In this sample's custom implementation of BaseVideoCapturer (CameraVideoCapturer)
the `initCapture()` it also calls the `init()` method of the FrontCameraFrameProvider class:

    @Override
    public void init() {
        mCapturerHasStarted = false;
        mCapturerIsPaused = false;

        mCapturerSettings = new CaptureSettings();
        mCapturerSettings.height = mHeight;
        mCapturerSettings.width = mWidth;
        mCapturerSettings.expectedDelay = 0;
        mCapturerSettings.format = BaseVideoCapturer.NV21;

        mHelper.init();
    }

The `FrontCameraFrameProvider.init()` method creates a Camera object.

The `BaseVideoCapturer.startCapture()` method of is called when a Publisher using the video capturer
starts capturing video to send as a stream to the OpenTok session. This will occur after the
`Session.publish(publisher)` method is called. The CameraVideoCapturer of this method calls the
`startCapture()` method of the FrontCameraFrameProvider object:

    @Override
    public int startCapture() {
        mCapturerHasStarted = true;
        mHelper.startCapture();
        return 0;
    }

The `FrontCameraFrameProvider.startCapture()` method sets some parameters of the Android Camera,
based on parameters set at initialization. It creates a callback buffer for the camera (calling
the `Camera.addCallbackBuffer(buffer)` method). It also sets itself as the implementer of the
Camera.PreviewCallback interface, and it calls the `startPreview()` method of the Camera object:

    mCamera.setPreviewCallbackWithBuffer(this);
    mCamera.startPreview();

The FrontCameraFrameProvider implementation of the
`FrontCameraFrameProvider.onPreviewFrame(data, camera)` method. This method is called when
preview frames are displayed by the camera. The FrontCameraFrameProvider calls the
`cameraFrameReady()` method of the ProviderListener object, which is the CameraVideoCapturer
object in this app:

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        mPreviewBufferLock.lock();
        if (data.length == mExpectedFrameSize) {
            // Get the rotation of the camera
            int currentRotation = compensateCameraRotation(mCurrentDisplay
                    .getRotation());

            if (mListener != null) {
                mListener.cameraFrameReady(data, mCaptureActualWidth, mCaptureActualHeight,
                        currentRotation, isFrontCamera());
            }

            // Reuse the video buffer
            camera.addCallbackBuffer(data);
        }
        mPreviewBufferLock.unlock();
    }

The CameraVideoCapturer implementation of the `cameraFrameReady()` method (defined by the
Camera.PreviewCallback interface) calls the `provideByteArrayFrame()` method, defined by the
BaseVideoCapturer class (which the CameraVideoCapturer class extends):

    @Override
    public void cameraFrameReady(byte[] data, int width, int height, int rotation, boolean mirror) {
        provideByteArrayFrame(data, NV21, width,
                height, rotation, mirror);
    }

This sends a byte array of data to the publisher, to be used for the next video frame published.

Note that the CameraVideoCapturer also includes code for determining which camera is being used,
working with the camera orientation and rotation, and determining the image size.

# Screen sharing (screensharing)

To see the code for this sample, switch to the screensharing branch. This branch shows you
how to capture the screen (an Android View) using a custom video capturer.

Before studying this sample, see the basic-capturer-step.1 sample.

This sample code demonstrates how to use the OpenTok Android SDK to publish a screen-sharing video,
using the device screen as the source for the stream's video.

The ChatActivity class uses WebView object as the source for the screen-sharing video in the
published stream.

In the `initializePublisher()` method of the ChatActivity class, after creating a Publisher object
the code calls the `setCapturer(capturer)` method of the Publisher object, passing in a
ScreensharingCapturer object:

    mPublisher.setCapturer(new ScreensharingCapturer(mScreensharedView));

ScreensharingCapturer is a custom class that extends the BaseVideoCapturer class (defined
in the OpenTok iOS SDK). This class lets you define a custom video capturer to be used
by an OpenTok publisher. The constructor of the ScreensharingCapturer class is passed an Android
View object, which it will use as the source for the video:

    public ScreensharingCapturer(View view) {
        mContentView = view;
        mFrameProducerHandler = new Handler();
    }

The constructor also creates a new Handler object to process the `mFrameProducer` Runnable object.

The `initCapture` method is used to initialize the capture and sets value for the pixel format of
an OTVideoFrame object. In this  example, it is set to RGB.

The `BaseVideoCapturer.init()` method initializes capture settings to be used by the custom
video capturer. In this sample's custom implementation of BaseVideoCapturer (ScreensharingCapturer)
the `initCapture()` it also sets some settings for the video capturer:

    @Override
    public void init() {
        mCapturerHasStarted = false;
        mCapturerIsPaused = false;

        mCapturerSettings = new CaptureSettings();
        mCapturerSettings.fps = FPS;
        mCapturerSettings.width = mWidth;
        mCapturerSettings.height = mHeight;
        mCapturerSettings.format = BaseVideoCapturer.ARGB;
    }

The `startCapture()` method starts the `mFrameProducer` thread after 1/15 second:

    @Override
    public int startCapture() {
        mCapturerHasStarted = true;
        mFrameProducerHandler.postDelayed(mFrameProducer, mFrameProducerIntervalMillis);
        return 0;
    }

The mFrameProducer` thread gets a Bitmap representation of the `mContentView` object
(the WebView), writes its pixels to a buffer, and then calls the `provideIntArrayFrame()`
method, passing in that buffer:

    private Runnable mFrameProducer = new Runnable() {
        @Override
        public void run() {
            int width = mContentView.getWidth();
            int height = mContentView.getHeight();

            if (frameBuffer == null || mWidth != width || mHeight != height) {
                mWidth = width;
                mHeight = height;
                frameBuffer = new int[mWidth * mHeight];
            }

            mContentView.setDrawingCacheEnabled(true);
            mContentView.buildDrawingCache();
            Bitmap bmp = mContentView.getDrawingCache();
            if (bmp != null) {
                bmp.getPixels(frameBuffer, 0, width, 0, 0, width, height);
                mContentView.setDrawingCacheEnabled(false);
                provideIntArrayFrame(frameBuffer, ARGB, width, height, 0, false);
            }

            if (mCapturerHasStarted && !mCapturerIsPaused) {
                mFrameProducerHandler.postDelayed(mFrameProducer, mFrameProducerIntervalMillis);
            }
        }
    };

The `provideIntArrayFrame()` method, defined by the BaseVideoCapturer class (which the
CameraVideoCapturer class extends) sends an integer array of data to the publisher, to be used
for the next video frame published.

If the publisher is still capturing video, the thread then starts again after another 1/15 of a
second, so that the capturer continues to supply the publisher with new video frames to publish.

Other resources
---------------

See the following:

* [API reference] [4] -- Provides details on the OpenTok Android API
* [Tutorials] [5] -- Includes conceptual information and code samples for all OpenTok features

[1]: https://tokbox.com/opentok/libraries/client/android/
[2]: http://developer.android.com/reference/android/widget/ArrayAdapter.html
[3]: http://developer.android.com/reference/android/widget/ListView.html
[4]: https://tokbox.com/opentok/libraries/client/android/reference/
[5]: https://tokbox.com/opentok/tutorials/


text:
/* istanbul ignore if */
if(typeof(luga) === "undefined"){
	throw("Unable to find Luga JS Common");
}

/**
 * @typedef {Object} luga.data.dataSourceChanged
 *
 * @property {luga.data.DataSet|luga.data.DetailSet} dataSource
 */

(function(){
	"use strict";

	luga.namespace("luga.data");

	/** @type {hash.<luga.data.DataSet>} */
	luga.data.dataSourceRegistry = {};

	luga.data.CONST = {
		COL_TYPES: ["date", "number", "string"],
		EVENTS: {
			CURRENT_ROW_CHANGED: "currentRowChanged",
			DATA_CHANGED: "dataChanged",
			DATA_SORTED: "dataSorted",
			DATA_LOADING: "dataLoading",
			PRE_DATA_SORTED: "preDataSorted",
			STATE_CHANGED: "stateChanged",
			XHR_ERROR: "xhrError"
		},
		ERROR_MESSAGES: {
			DUPLICATED_UUID: "Unable to register dataSource. The uuuid was already used: {0}",
			INVALID_FILTER_PARAMETER: "Invalid filter. You must use a function as filter",
			INVALID_FILTER_ACTION: "Invalid action from a filter function. A filter must return either a plain object or null (undefined, primitives etc are not valid return values)",
			INVALID_UPDATER_PARAMETER: "Invalid updater. You must use a function as updater",
			INVALID_UPDATER_ACTION: "Invalid action from an updater function. An updater must return a plain object (null, undefined, primitives etc are not valid return values)",
			INVALID_STATE: "luga.data.utils.assembleStateDescription: Unsupported state: {0}"
		},
		PK_KEY: "lugaRowId",
		PK_KEY_PREFIX: "lugaPk_",
		XHR_TIMEOUT: 10000 // Keep this accessible to everybody
	};

	/**
	 * Returns a dataSource from the registry
	 * Returns null if no source matches the given id
	 * @param {string} uuid
	 * @return {luga.data.DataSet|luga.data.DetailSet}
	 */
	luga.data.getDataSource = function(uuid){
		if(luga.data.dataSourceRegistry[uuid] !== undefined){
			return luga.data.dataSourceRegistry[uuid];
		}
		return null;
	};

	/**
	 * Adds a dataSource inside the registry
	 * @param {string}                                uuid
	 * @param {luga.data.DataSet|luga.data.DetailSet} dataSource
	 * @throw {Exception}
	 */
	luga.data.setDataSource = function(uuid, dataSource){
		if(luga.data.getDataSource(uuid) !== null){
			throw(luga.string.format(luga.data.CONST.ERROR_MESSAGES.DUPLICATED_UUID, [uuid]));
		}
		luga.data.dataSourceRegistry[uuid] = dataSource;
	};

	/**
	 * @typedef {string} luga.data.STATE
	 * @enum {string}
	 */
	luga.data.STATE = {
		ERROR: "error",
		LOADING: "loading",
		READY: "ready"
	};

	luga.namespace("luga.data.utils");

	/**
	 * @typedef {Object} luga.data.stateDescription
	 *
	 * @property {null|luga.data.STATE}  state
	 * @property {boolean}          isStateLoading
	 * @property {boolean}          isStateError
	 * @property {boolean}          isStateReady
	 */

	/**
	 * Given a state string, returns an object containing a boolean field for each possible state
	 * @param {null|luga.data.STATE} state
	 * @throw {Exception}
	 * @return {luga.data.stateDescription}
	 */
	luga.data.utils.assembleStateDescription = function(state){
		if((state !== null) && (luga.data.utils.isValidState(state) === false)){
			throw(luga.string.format(luga.data.CONST.ERROR_MESSAGES.INVALID_STATE, [state]));
		}
		return {
			state: state,
			isStateError: (state === luga.data.STATE.ERROR),
			isStateLoading: (state === luga.data.STATE.LOADING),
			isStateReady: (state === luga.data.STATE.READY)
		};
	};

	/**
	 * Apply the given filter function to each passed row
	 * Return an array of filtered rows
	 * @param {Array.<luga.data.DataSet.row>} rows    Required
	 * @param {Function}                      filter  Required
	 * @param {luga.data.DataSet}             dataset Required
	 * @return {Array.<luga.data.DataSet.row>}
	 * @throw {Exception}
	 */
	luga.data.utils.filter = function(rows, filter, dataset){
		if(luga.type(filter) !== "function"){
			throw(luga.data.CONST.ERROR_MESSAGES.INVALID_FILTER_PARAMETER);
		}
		const retRows = [];
		for(let i = 0; i < rows.length; i++){
			const filteredRow = filter(rows[i], i, dataset);
			// Row to be removed
			if(filteredRow === null){
				continue;
			}
			// Invalid row
			if(luga.isPlainObject(filteredRow) === false){
				throw(luga.data.CONST.ERROR_MESSAGES.INVALID_FILTER_ACTION);
			}
			// Valid row
			retRows.push(filteredRow);
		}
		return retRows;
	};

	/**
	 * Apply the given updater function to each passed row
	 * @param {Array.<luga.data.DataSet.row>} rows      Required
	 * @param {Function}                      formatter Required
	 * @param {luga.data.DataSet}             dataset   Required
	 * @throw {Exception}
	 */
	luga.data.utils.update = function(rows, formatter, dataset){
		if(luga.type(formatter) !== "function"){
			throw(luga.data.CONST.ERROR_MESSAGES.INVALID_UPDATER_ACTION);
		}
		for(let i = 0; i < rows.length; i++){
			const formattedRow = formatter(rows[i], i, dataset);
			if(luga.isPlainObject(formattedRow) === false){
				throw(luga.data.CONST.ERROR_MESSAGES.INVALID_UPDATER_ACTION);
			}
		}
	};

	/**
	 * Return true if the passed state is supported
	 * @param {string}  state
	 * @return {boolean}
	 */
	luga.data.utils.isValidState = function(state){
		for(let key in luga.data.STATE){
			if(luga.data.STATE[key] === state){
				return true;
			}
		}
		return false;
	};

}());

redpajama_set_name: RedPajamaGithub


text:
package com.proteus.peach.redis.manager

import java.util.{Map => JMap}
import java.util.concurrent.ConcurrentHashMap
import java.util.{List => JList}

import com.proteus.peach.common.util.JavaCollectionsConversions.scalaToJavaConsumer
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import redis.clients.jedis.Jedis
import redis.clients.jedis.JedisPool
import redis.clients.jedis.JedisPoolConfig
import redis.clients.jedis.exceptions.JedisConnectionException

import scala.util.Failure
import scala.util.Success
import scala.util.Try

/**
 * Redis session manager. For each session, a pool of available connections is maintained, each
 * thread accessing Redis must request and return the Jedis connection to the pool when it
 * finishes using it.
 */
object RedisSessionManager {

  /**
   * Pool maximum size.
   */
  private val PoolSize: Int = 64

  /**
   * Wait time to retrieve elements from the pool.
   */
  private val PoolWaitTime: Int = 10000

  /**
   * Class logger.
   */
  private val Log: Logger = LoggerFactory.getLogger(this.getClass.getName)

  /**
   * Map of available pools indexed by session identifier.
   */
  private val Sessions: JMap[String, JedisPool] = new ConcurrentHashMap[String, JedisPool]()

  /**
   * Map of session configurations per identifier.
   */
  private val Configurations: JMap[String, BasicRedisSession] = new ConcurrentHashMap[String, BasicRedisSession]()

  /**
   * Initialize a session in redis. This method must be called before using the connection.
   *
   * @param redisSession The redis session.
   * @return Whether a session could be established with the redis host.
   */
  def init(redisSession: BasicRedisSession): Boolean = {
    if (this.Sessions.containsKey(redisSession.id)) {
      throw new UnsupportedOperationException(
        s"A pool for session ${redisSession.id} already exists")
    }
    val pool = this.connect(redisSession)
    if (pool.isDefined) {
      this.Configurations.put(redisSession.id, redisSession)
      this.Sessions.put(redisSession.id, pool.get)
    }
    pool.isDefined
  }

  /**
   * Establish a connection with redis.
   *
   * @param redisSession The redis session.
   * @return An option with a JedisPool.
   */
  private def connect(redisSession: BasicRedisSession): Option[JedisPool] = {
    val poolConfig = new JedisPoolConfig()
    poolConfig.setMaxWaitMillis(RedisSessionManager.PoolWaitTime)
    Log.debug("Maximum number of connections: ${poolConfig.getMaxTotal}")
    poolConfig.setMaxTotal(RedisSessionManager.PoolSize)
    poolConfig.setTestOnBorrow(true)
    poolConfig.setTestWhileIdle(true)
    val connectionPool = new JedisPool(
      poolConfig, redisSession.addresses.get(0), redisSession.port)
    Try {
      connectionPool.getResource
    } match {
      case Success(client) => {
        if (client.isConnected) {
          client.close()
          Some(connectionPool)
        } else {
          None
        }
      }
      case Failure(error)=> {
        Log.error(s"Cannot connect to redis ${redisSession}", error)
        None
      }
    }
  }

  /**
   * Method to reconnect.
   *
   * @param sessionId The session identifier.
   * @return An option with the Jedis client.
   */
  def reconnect(sessionId: String): Option[Jedis] = {
    if (this.Sessions.containsKey(sessionId)) {
      // close the pool
      val oldPool = this.Sessions.get(sessionId)
      if (!oldPool.isClosed) {
        oldPool.close()
      }
      val newPool = this.connect(this.Configurations.get(sessionId))

      this.Sessions.put(sessionId, newPool.get)
      Some(this.Sessions.get(sessionId).getResource)
    }
    None

  }

  /**
   * Shutdown all managed sessions.
   */
  def shutdown(): Unit = {
    this.Sessions.keySet().forEach((sessionId: String) => this.close(sessionId))
  }

  /**
   * Close the connections associated with a given session.
   *
   * @param sessionId The session identifier.
   */
  def close(sessionId: String): Unit = {
    if (this.Sessions.containsKey(sessionId)) {
      val pool: JedisPool = this.Sessions.get(sessionId)
      this.Sessions.remove(sessionId)
      pool.close()
    }
  }

  /**
   * Get a Jedis client that can be used to send data to redis. Notice that the client thread
   * must call jedis.close() in order for the client to be returned to the connection pool.
   *
   * @param sessionId The session identifier.
   * @return An option with the Jedis client.
   */
  def getSession(sessionId: String): Option[Jedis] = {
    if (this.Sessions.containsKey(sessionId)) {
      Option(this.Sessions.get(sessionId).getResource)
    } else {
      None
    }
  }

  /**
   * Get the redis session used to identify a session.
   *
   * @param sessionId The session identifier.
   * @return A basic redis session information.
   */
  def getConfiguration(sessionId: String): Option[BasicRedisSession] = {
    if (this.Configurations.containsKey(sessionId)) {
      Option(this.Configurations.get(sessionId))
    } else {
      None
    }
  }
}


text:
DROP VIEW IF EXISTS v_account_balance2;

CREATE VIEW v_account_balance2
AS
WITH postings AS (
  SELECT account_id, posting_date, amount
  FROM posting_header NATURAL JOIN posting_detail
)
SELECT
  p1.account_id,
  (SELECT name FROM account where account_id = p1.account_id) AS account_name,
  p1.posting_date,
  SUM(p2.amount) AS amount,
  SUM(CASE WHEN p1.posting_date = p2.posting_date THEN 1 ELSE 0 END) AS cnt
FROM (SELECT DISTINCT account_id, posting_date FROM postings) p1
JOIN postings p2
  ON p2.account_id = p1.account_id
  AND p2.posting_date <= p1.posting_date
WHERE EXISTS (
  SELECT NULL FROM account
  WHERE account_id = p1.account_id
  AND account_type IN ('A', 'L')
)
GROUP BY p1.account_id, p1.posting_date
ORDER BY p1.account_id, p1.posting_date
;


text:
Dashbuilder is a general purpose dashboard and reporting web app which allows for:

Visual configuration and personalization of dashboards

Support for different types of visualizations using several charting libraries

Full featured editor for the definition of chart visualizations

Definition of interactive report tables

Data extraction from external systems, through different protocols

Support for both analytics and real-time dashboards

Upcoming features

New renderers based on D3 JS & Lienzo GWT

UI editor for the creation of data sets definitions

Hierarchical (nested group) displayer types: Tree & Pie

Support for multiple data series

Rich mobility support

Alerts and SLA configuration

RESTful API

Architecture

Not tied to any chart rendering technology. Pluggable renderers.

No tied to any data storage.

Ability to read data from: CSV files, Databases, Elastic Search or a Java generator.

Decoupled client & server layers. Ability to build pure lightweight client dashboards.

Ability to push & handle data sets on client for better performance.

Based on Uberfire, a framework for building rich workbench styled apps on the web.

For further information, please visit the project web site dashbuilder.org

Licensed under the Apache License, Version 2.0


text:
module Test.Eval where

import Test.TestUtils

evalTests =
    [ run "Test/Eval/EvalDiamond.pkg" "D-v1.0"
    ]


text:
{{<layout}}

{{$pageTitle}}
  GAIN CAPITAL Design Kit
{{/pageTitle}}

{{$content}}

  <link href="/public/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
  <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700,600,300' rel='stylesheet' type='text/css'>

Radio buttons

  <form action="basic-branch" method="get" class="form">
    <input type="hidden" name="nextlink" value="q2-platforms">

    <input id="radio-inline-1a" type="radio" name="reftype" value="used">
    I've used trading platforms before

    <input id="radio-inline-1b" type="radio" name="reftype" value="notused">
    I've not used any trading platforms

    <input type="submit" class="primary-button" value="Continue" style="padding:0.6em 1.5em">

  </form>

GAIN CAPITAL Design patterns BETA

Fundamental
elements

Text

Colours

Prices

Tables

Buttons

Checkboxes

Menus

Icons

Form fields

Tool tips

Panels

Show/Hide controllers

Platform
components

Trade ticket

Watch lists

Open orders

News headlines

News story

{{/content}}

{{/layout}}


text:
/**
 * @class   vtkMultiViewportImageProcessingPass
 * @brief   Convenient class for post-processing passes.
 * render pass.
 *
 * Abstract class with some convenient methods frequently used in subclasses.
 * This class was a modification to the original vtkImageProcessingPass class to support
 * rendering in multiple view-port.
 *
 *
 * @sa
 * vtkRenderPass vtkGaussianBlurPass vtkSobelGradientMagnitudePass
*/

#ifndef vtkMultiViewportImageProcessingPass_h
#define vtkMultiViewportImageProcessingPass_h

#include "vtkRobartsVisualizationExport.h" // For export macro

// VTK includes
#include <vtkRenderPass.h>
#include <vtkVersionMacros.h>

class vtkOpenGLRenderWindow;
class vtkDepthPeelingPassLayerList; // Pimpl
#if VTK_MAJOR_VERSION >= 8
  class vtkOpenGLFramebufferObject;
  typedef vtkOpenGLFramebufferObject RobartsVTKFrameBufferObject;
#else
  class vtkFrameBufferObject;
  typedef vtkFrameBufferObject RobartsVTKFrameBufferObject;
#endif
class vtkTextureObject;

class vtkRobartsVisualizationExport vtkMultiViewportImageProcessingPass : public vtkRenderPass
{
public:
  vtkTypeMacro(vtkMultiViewportImageProcessingPass, vtkRenderPass);
  void PrintSelf(ostream& os, vtkIndent indent);

  /**
   * Release graphics resources and ask components to release their own
   * resources.
   * \pre w_exists: w!=0
   */
  void ReleaseGraphicsResources(vtkWindow* w);

  //@{
  /**
   * Delegate for rendering the image to be processed.
   * If it is NULL, nothing will be rendered and a warning will be emitted.
   * It is usually set to a vtkCameraPass or to a post-processing pass.
   * Initial value is a NULL pointer.
   */
  vtkGetObjectMacro(DelegatePass, vtkRenderPass);
  virtual void SetDelegatePass(vtkRenderPass* delegatePass);
  //@}

protected:
  /**
   * Default constructor. DelegatePass is set to NULL.
   */
  vtkMultiViewportImageProcessingPass();

  /**
   * Destructor.
   */
  virtual ~vtkMultiViewportImageProcessingPass();

  /**
   * Render delegate with a image of different dimensions than the
   * original one.
   * \pre s_exists: s!=0
   * \pre fbo_exists: fbo!=0
   * \pre fbo_has_context: fbo->GetContext()!=0
   * \pre target_exists: target!=0
   * \pre target_has_context: target->GetContext()!=0
   */
  void RenderDelegate(const vtkRenderState* s,
                      int width,
                      int height,
                      int newWidth,
                      int newHeight,
                      RobartsVTKFrameBufferObject* fbo,
                      vtkTextureObject* target);

  vtkRenderPass* DelegatePass;

private:
  vtkMultiViewportImageProcessingPass(const vtkMultiViewportImageProcessingPass&) VTK_DELETE_FUNCTION;
  void operator=(const vtkMultiViewportImageProcessingPass&) VTK_DELETE_FUNCTION;
};

#endif


text:
SHELL := /bin/bash

# All the packages that we care about for linting, testing, etc.
SRC_PACKAGES=.

TF_IOS13_TEST=../tensorflow/test
MIL_TEST="."
MIL_TEST_INTERNAL="../../../../coremltools-internal/coremltools_internal/converters/mil"

.PHONY: all lint test style checkstyle

PYTHON ?= $(shell command -v python || command -v python)

all: checkstyle lint test

style:
	${PYTHON} -m yapf -rip --verify ${SRC_PACKAGES}

checkstyle:
	${PYTHON} -m yapf -rdp ${SRC_PACKAGES}

lint:
	${PYTHON} -m pylint -j 0 ${SRC_PACKAGES}

test:
	${PYTHON} -m pytest -W ignore::DeprecationWarning ${MIL_TEST} ${MIL_TEST_INTERNAL}

test_ref:
	${PYTHON} -m pytest -W ignore::DeprecationWarning ${TF_IOS13_TEST}


text:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using AutoMapper;
using Hinata.Data.Commands;
using Hinata.Markdown;
using Hinata.Models;
using Hinata.Web.Mvc;

namespace Hinata.Controllers
{
    public class DraftController : WindowsAuthenticationContoller
    {
        private readonly DraftDbCommand _draftDbCommand = new DraftDbCommand(GlobalSettings.DefaultConnectionString);
        private readonly ItemDbCommand _itemDbCommand = new ItemDbCommand(GlobalSettings.DefaultConnectionString);

        [Route("draft")]
        [HttpGet]
        public async Task<ActionResult> Index()
        {
            var drafts = await _draftDbCommand.GetByAuthorAsync(LogonUser);
            var model = Mapper.Map<IEnumerable<DraftIndexModel>>(drafts);
            return View(model);
        }

        [Route("draft/new")]
        [HttpGet]
        public ActionResult New(ItemType itemType = ItemType.Article)
        {
            var draft = Draft.NewDraft(LogonUser, itemType);
            var model = Mapper.Map<DraftEditModel>(draft);

            ViewBag.Title = "新規作成";

            return View("Edit", model);
        }

        [Route("draft/{id}/edit")]
        [HttpGet]
        public async Task<ActionResult> Edit(string id)
        {
            var draft = await _draftDbCommand.FindAsync(id);
            if (draft == null)
            {
                var item = await _itemDbCommand.FindAsync(id);
                if (item == null) return HttpNotFound();
                draft = item.ToDraft();
                await _draftDbCommand.SaveAsync(draft);
            }

            if (string.IsNullOrWhiteSpace(draft.Title))
            {
                ViewBag.Title = "編集";
            }
            else
            {
                ViewBag.Title = "編集: " + draft.Title;
            }

            var model = Mapper.Map<DraftEditModel>(draft);
            return View(model);
        }

        [Route("draft/save")]
        [HttpPost]
        public async Task<ActionResult> Save(DraftEditModel model)
        {
            model.EntryMode = EntryMode.SaveDraft;
            TryValidateModel(model);

            if (!ModelState.IsValid)
            {
                using (var parser = new MarkdownParser())
                {
                    model.Html = parser.Transform(model.Body);
                    return View("Edit", model);
                }
            }

            var draft = await _draftDbCommand.FindAsync(model.Id) ?? Draft.NewDraft(LogonUser, model.ItemType);
            Mapper.Map(model, draft);

            draft.LastModifiedDateTime = DateTime.Now;
            await _draftDbCommand.SaveAsync(draft);
            return RedirectToAction("Index", "Draft");
        }

        [Route("draft/publish")]
        [HttpPost]
        public async Task<ActionResult> Publish(DraftEditModel model)
        {
            model.EntryMode = EntryMode.PublishItem;
            TryValidateModel(model);

            if (!ModelState.IsValid)
            {
                using (var parser = new MarkdownParser())
                {
                    model.Html = parser.Transform(model.Body);
                    return View("Edit", model);
                }
            }

            var draft = await _draftDbCommand.FindAsync(model.Id) ?? Draft.NewDraft(LogonUser, model.ItemType);
            Mapper.Map(model, draft);
            var item = draft.ToItem();

            await _itemDbCommand.SaveAsync(item);
            await _draftDbCommand.DeleteAsync(draft.Id);

            return RedirectToAction("Index", "Item");
        }

        [Route("draft/autosave")]
        [HttpPost]
        public async Task<JsonResult> AutoSave(DraftEditModel model)
        {
            var draft = Mapper.Map<Draft>(model);
            draft.Author = LogonUser;
            draft.LastModifiedDateTime = DateTime.Now;
            await _draftDbCommand.SaveAsync(draft);

            return Json(new { draft.Id, Url = Url.Action("Edit", "Draft", new { id = draft.Id }) });
        }

        [Route("draft/html")]
        [HttpPost]
        public ContentResult ConvertToHtml(string markdown)
        {
            using (var parser = new MarkdownParser())
            {
                return new ContentResult
                {
                    Content = parser.Transform(markdown),
                    ContentEncoding = Encoding.UTF8,
                    ContentType = "text/html"
                };
            }
        }

        [Route("draft/{id}/preview")]
        [HttpGet]
        public async Task<ActionResult> Preview(string id)
        {
            var draft = await _draftDbCommand.FindAsync(id);
            if (draft == null) return HttpNotFound();

            var model = Mapper.Map<DraftPreviewModel>(draft);

            return PartialView("_Preview", model);
        }

        [HttpGet]
        [Route("draft/{id}/delete")]
        public async Task<ActionResult> Delete(string id)
        {
            var draft = await _draftDbCommand.FindAsync(id);
            if (draft == null || draft.Author != LogonUser) return HttpNotFound();

            await _draftDbCommand.DeleteAsync(id);

            return RedirectToAction("Index");
        }
    }
}


text: assert(Number.prototype.toString(2) === "0");


text:
'use strict';

chemistryApp.directive('myCheckbox', function($log) {
    $log.info('we are in');
    return {
        link: function (scope,elemennt,attrs,controller) {
            var markup = "Test
";
            var markup2 = "<input type='text'ng-model='sampleData' /> {{sampleData}}
 ";
            angular.element(element).html(markup2);
        }
}
});


text:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PracticeTime.Web.Models;

namespace PracticeTime.Web.Test.Models
{
    [TestClass]
    public class NameValueTest
    {
        [TestMethod]
        public void ConstructorTest()
        {
            NameValue nameValue = new NameValue(){Name="foo",Value = "bar"};
            Assert.IsNotNull(nameValue.Name);
            Assert.IsNotNull(nameValue.Value);
        }
    }
}


text:
<?php

namespace Dhii\Shell\Command;

/**
 * Something that can be a command line value.
 *
 * @since [*next-version*]
 *
 * @author Dhii Team <development@dhii.co>
 */
interface ValueInterface
{
    /**
     * @since [*next-version*]
     *
     * @return The string representation of the named value.
     */
    public function __toString();

    /**
     * @since [*next-version*]
     *
     * @return string The string representation of only the value.
     */
    public function getValue();
}


text:
.class Lcom/android/server/AlarmManagerService$4;
.super Ljava/lang/Object;
.source "AlarmManagerService.java"

# interfaces
.implements Ljava/util/Comparator;

# annotations
.annotation system Ldalvik/annotation/EnclosingMethod;
    value = Lcom/android/server/AlarmManagerService;->dumpImpl(Ljava/io/PrintWriter;[Ljava/lang/String;)V
.end annotation

.annotation system Ldalvik/annotation/InnerClass;
    accessFlags = 0x0
    name = null
.end annotation

.annotation system Ldalvik/annotation/Signature;
    value = {
        "Ljava/lang/Object;",
        "Ljava/util/Comparator",
        "<",
        "Lcom/android/server/AlarmManagerService$FilterStats;",
        ">;"
    }
.end annotation

# instance fields
.field final synthetic this$0:Lcom/android/server/AlarmManagerService;

# direct methods
.method constructor <init>(Lcom/android/server/AlarmManagerService;)V
    .locals 0

    .prologue
    .line 1466
    iput-object p1, p0, Lcom/android/server/AlarmManagerService$4;->this$0:Lcom/android/server/AlarmManagerService;

    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    return-void
.end method

# virtual methods
.method public compare(Lcom/android/server/AlarmManagerService$FilterStats;Lcom/android/server/AlarmManagerService$FilterStats;)I
    .locals 4
    .param p1, "lhs"    # Lcom/android/server/AlarmManagerService$FilterStats;
    .param p2, "rhs"    # Lcom/android/server/AlarmManagerService$FilterStats;

    .prologue
    .line 1469
    iget-wide v0, p1, Lcom/android/server/AlarmManagerService$FilterStats;->aggregateTime:J

    iget-wide v2, p2, Lcom/android/server/AlarmManagerService$FilterStats;->aggregateTime:J

    cmp-long v0, v0, v2

    if-gez v0, :cond_0

    .line 1470
    const/4 v0, 0x1

    .line 1474
    :goto_0
    return v0

    .line 1471
    :cond_0
    iget-wide v0, p1, Lcom/android/server/AlarmManagerService$FilterStats;->aggregateTime:J

    iget-wide v2, p2, Lcom/android/server/AlarmManagerService$FilterStats;->aggregateTime:J

    cmp-long v0, v0, v2

    if-lez v0, :cond_1

    .line 1472
    const/4 v0, -0x1

    goto :goto_0

    .line 1474
    :cond_1
    const/4 v0, 0x0

    goto :goto_0
.end method

.method public bridge synthetic compare(Ljava/lang/Object;Ljava/lang/Object;)I
    .locals 1
    .param p1, "x0"    # Ljava/lang/Object;
    .param p2, "x1"    # Ljava/lang/Object;

    .prologue
    .line 1466
    check-cast p1, Lcom/android/server/AlarmManagerService$FilterStats;

    .end local p1    # "x0":Ljava/lang/Object;
    check-cast p2, Lcom/android/server/AlarmManagerService$FilterStats;

    .end local p2    # "x1":Ljava/lang/Object;
    invoke-virtual {p0, p1, p2}, Lcom/android/server/AlarmManagerService$4;->compare(Lcom/android/server/AlarmManagerService$FilterStats;Lcom/android/server/AlarmManagerService$FilterStats;)I

    move-result v0

    return v0
.end method


text:
using BepuPhysics;
using BepuPhysics.Collidables;
using BepuPhysics.Constraints;
using BepuUtilities;
using System.Numerics;

namespace HeadlessTests24.StreamerStyle.Scenes;
public class Colosseum : Scene
{
    public static void CreateRingWall(Simulation simulation, Vector3 position, Box ringBoxShape, BodyDescription bodyDescription, int height, float radius)
    {
        var circumference = MathF.PI * 2 * radius;
        var boxCountPerRing = (int)(0.9f * circumference / ringBoxShape.Length);
        float increment = MathHelper.TwoPi / boxCountPerRing;
        for (int ringIndex = 0; ringIndex < height; ringIndex++)
        {
            for (int i = 0; i < boxCountPerRing; i++)
            {
                var angle = ((ringIndex & 1) == 0 ? i + 0.5f : i) * increment;
                bodyDescription.Pose = new RigidPose(
                    position + new Vector3(-MathF.Cos(angle) * radius, (ringIndex + 0.5f) * ringBoxShape.Height, MathF.Sin(angle) * radius),
                    QuaternionEx.CreateFromAxisAngle(Vector3.UnitY, angle));
                simulation.Bodies.Add(bodyDescription);
            }
        }
    }

    public static void CreateRingPlatform(Simulation simulation, Vector3 position, Box ringBoxShape, BodyDescription bodyDescription, float radius)
    {
        var innerCircumference = MathF.PI * 2 * (radius - ringBoxShape.HalfLength);
        var boxCount = (int)(0.95f * innerCircumference / ringBoxShape.Height);
        float increment = MathHelper.TwoPi / boxCount;
        for (int i = 0; i < boxCount; i++)
        {
            var angle = i * increment;
            bodyDescription.Pose = new RigidPose(
                position + new Vector3(-MathF.Cos(angle) * radius, ringBoxShape.HalfWidth, MathF.Sin(angle) * radius),
                QuaternionEx.Concatenate(QuaternionEx.CreateFromAxisAngle(Vector3.UnitZ, MathF.PI * 0.5f), QuaternionEx.CreateFromAxisAngle(Vector3.UnitY, angle + MathF.PI * 0.5f)));
            simulation.Bodies.Add(bodyDescription);
        }
    }

    public static Vector3 CreateRing(Simulation simulation, Vector3 position, Box ringBoxShape, BodyDescription bodyDescription, float radius, int heightPerPlatformLevel, int platformLevels)
    {
        for (int platformIndex = 0; platformIndex < platformLevels; ++platformIndex)
        {
            var wallOffset = ringBoxShape.HalfLength - ringBoxShape.HalfWidth;
            CreateRingWall(simulation, position, ringBoxShape, bodyDescription, heightPerPlatformLevel, radius + wallOffset);
            CreateRingWall(simulation, position, ringBoxShape, bodyDescription, heightPerPlatformLevel, radius - wallOffset);
            CreateRingPlatform(simulation, position + new Vector3(0, heightPerPlatformLevel * ringBoxShape.Height, 0), ringBoxShape, bodyDescription, radius);
            position.Y += heightPerPlatformLevel * ringBoxShape.Height + ringBoxShape.Width;
        }
        return position;
    }

    public static void CreateColosseum(Simulation simulation, Vector3 position, Box ringBoxShape, BodyDescription boxDescription, float innerRadius, float ringSpacing, int layerCount, int platformsPerLayer, int heightPerPlatform)
    {
        for (int layerIndex = 0; layerIndex < layerCount; ++layerIndex)
        {
            var ringCount = layerCount - layerIndex;
            for (int ringIndex = 0; ringIndex < ringCount; ++ringIndex)
            {
                CreateRing(simulation, position, ringBoxShape, boxDescription, innerRadius + ringIndex * (ringBoxShape.Length + ringSpacing) + layerIndex * (ringBoxShape.Length - ringBoxShape.Width), heightPerPlatform, platformsPerLayer);
            }
            position.Y += platformsPerLayer * (ringBoxShape.Height * heightPerPlatform + ringBoxShape.Width);
        }
    }
    public override float TimestepDuration => 1 / 60f;
    public override Vector3 Gravity => new Vector3(0, -10, 0);

    public override void Initialize(Random random, int threadCount)
    {
        ThreadDispatcher = new ThreadDispatcher(threadCount);

        var layerRand = random.NextDouble();
        var heightPerRand = random.NextDouble();
        var platformsPerRand = random.NextDouble();
        var radiusRand = random.NextDouble();
        var heightPerPlatform = 2 + (int)(heightPerRand * heightPerRand * 5);
        var platformsPerLayer = 2 + (int)(platformsPerRand * platformsPerRand * 5);
        var layerCount = 3 + (int)(6 * layerRand * layerRand);
        var innerRadius = 3.5f + (float)(radiusRand * radiusRand * MathF.Max(0, 50 - layerCount * 6));

        //layerCount = 3;
        //heightPerPlatform = 2;
        //platformsPerLayer = 2;
        //innerRadius = 5;

        var dependencyChainLength = (1 + heightPerPlatform) * platformsPerLayer * layerCount;
        InterlockedBars.GetSimulationPropertiesForDependencyChain(dependencyChainLength, out var substepCount, out var hz, out var solverIterationCount);
        Simulation = Simulation.Create(BufferPool, new NarrowPhaseCallbacks() { SpringSettings = new SpringSettings(hz, 1) }, new DemoPoseIntegratorCallbacks(Gravity), new SolveDescription(solverIterationCount, substepCount));

        var ringBoxShape = new Box(0.5f, 1, 3);
        var ringBoxInertia = ringBoxShape.ComputeInertia(1);
        var boxDescription = BodyDescription.CreateDynamic(new Vector3(), ringBoxInertia, new(Simulation.Shapes.Add(ringBoxShape), 0.1f), -0.01f);

        var layerPosition = new Vector3();
        var ringSpacing = 0.5f;
        CreateColosseum(Simulation, layerPosition, ringBoxShape, boxDescription, innerRadius, ringSpacing, layerCount, platformsPerLayer, heightPerPlatform);

        CreateRegionOfInterest();

        //Console.WriteLine($"box count: {Simulation.Bodies.ActiveSet.Count}");
        Simulation.Statics.Add(new StaticDescription(new Vector3(0, -5, 0), Simulation.Shapes.Add(new Box(2500, 10, 2500))));
    }

    public override void Update()
    {
        Simulation.Timestep(TimestepDuration, ThreadDispatcher);
    }
}