How to Snoop On a channel In ARI

I am developing an ARI application to get real-time transcription, which is working fine with ChanSpy. Now I want to do the same in ARI, maybe using SnoopChannel, but I could not find any proper guide about it. I just want to know after which event we have to start snooping on a channel in ARI or any guide about it.

I am using same code from asterisk external media from guthub.

const client = require('ari-client');
const EventEmitter = require('events');

class AriController extends EventEmitter {

	constructor(options) {
		super();
		this.options = Object.assign({}, options);
	}

	async close() {
		if (this.closing) {
			return;
		}
		this.closing = true;

		if (this.localChannel) {
			console.log("Hanging up local channel");
			try {
				await this.localChannel.hangup();
			} catch (error) {
			}
			delete this.localChannel;
		}
		if (this.externalChannel) {
			console.log("Hanging up external media channel");
			try {
				await this.externalChannel.hangup();
			} catch (error) {
			}
			delete this.externalChannel;
		}
		if (this.bridge) {
			console.log("Destroying bridge");
			try {
				await this.bridge.destroy();
			} catch (error) {
			}
			delete this.bridge;
		}

		if (this.options.closeCallback) {
			this.options.closeCallback();
		}
		await this.ari.stop();
		this.emit('close');
	}

	async connect() {
		this.ari = await client.connect(
			this.options.ariServerUrl, this.options.ariUser, this.options.ariPassword);

		await this.ari.start("externalMedia");

		// Create a simple bridge that is controlled by ARI/Stasis
		this.bridge = this.ari.Bridge();
		try {
			await this.bridge.create({ type: "mixing" });
		} catch (error) {
			console.error(error);
			this.close();
		}
		this.bridge.on('BridgeDestroyed', (event) => {
			this.close();
		});

		/*
		 *  Create the local channel.  This actually creates 2
		 *  back to back channels, one that's controlled by ARI/Stasis
		 *  that we can put into the bridge we created above and 
		 *  another one the one that dials a phone, confbridge, etc.
		 *  and joins _that_ bridge. 
		 *  
		 *  localChannel below is actually the first channel. 
		 */
		this.localChannel = this.ari.Channel();
		this.localChannel.on('StasisStart', (event, chan) => {
			this.bridge.addChannel({ channel: chan.id });
		});
		this.localChannel.on('StasisEnd', (event, chan) => {
			this.close();
		});

		// Call the phone or confbridge specified in dialstring
		try {
			await this.localChannel.originate({
				endpoint: this.options.dialstring, formats: this.options.format, app: "externalMedia",
			});
		} catch (error) {
			this.close();
		}

		// Now we create the External Media channel.
		this.externalChannel = this.ari.Channel();
		this.externalChannel.on('StasisStart', (event, chan) => {
			this.bridge.addChannel({ channel: chan.id });
		});
		this.externalChannel.on('StasisEnd', (event, chan) => {
			this.close();
		});

		/*
		 * We give the external channel the address of the listener
		 * we already set up and the format it should stream in.
		 */
		try {
			let resp = await this.externalChannel.externalMedia({
				app: "externalMedia",
				external_host: this.options.listenServer,
				format: this.options.format
			});
			this.emit('ready');
		} catch (error) {
			this.close();
		}
	}
}

module.exports.AriController = AriController;

The example you provided does not show ChanSpy.

Whenever you have a channel, you can snoop on it. The Asterisk ARI documentation on snoop goes into more detail.

@penguinpbx I have ChanSpy in dialplan not here, this connects ARI externalMedia and ChanSpy and I’m getting the text transcript with ChanSpy when I speak but I am asking if there is a way we can get a transcript or if we can use snoop channel feature as what is done by ChanSpy.

To implement real-time transcription using ARI (Asterisk REST Interface), you can utilize the SnoopChannel application. This application allows you to listen in on an active channel and receive audio from it. However, to transcribe the audio in real-time, you’ll need to integrate a speech-to-text engine like Google Cloud Speech-to-Text or another similar service.

const client = require(‘ari-client’);
const EventEmitter = require(‘events’);

class AriController extends EventEmitter {
constructor(options) {
super();
this.options = Object.assign({}, options);
}

async close() {
    if (this.closing) {
        return;
    }
    this.closing = true;

    // Clean up channels and bridge
    for (const channel of [this.localChannel, this.externalChannel]) {
        if (channel) {
            console.log("Hanging up channel", channel.id);
            try {
                await channel.hangup();
            } catch (error) {
                console.error("Error hanging up channel:", error);
            }
            delete this[channel === this.localChannel ? "localChannel" : "externalChannel"];
        }
    }

    if (this.bridge) {
        console.log("Destroying bridge", this.bridge.id);
        try {
            await this.bridge.destroy();
        } catch (error) {
            console.error("Error destroying bridge:", error);
        }
        delete this.bridge;
    }

    // Stop ARI client
    try {
        await this.ari.stop();
    } catch (error) {
        console.error("Error stopping ARI client:", error);
    }

    this.emit('close');
}

async connect() {
    this.ari = await client.connect(
        this.options.ariServerUrl, this.options.ariUser, this.options.ariPassword);

    await this.ari.start("externalMedia");

    // Create a mixing bridge
    this.bridge = this.ari.Bridge();
    try {
        await this.bridge.create({ type: "mixing" });
    } catch (error) {
        console.error("Error creating bridge:", error);
        this.close();
        return;
    }
    this.bridge.on('BridgeDestroyed', (event) => {
        this.close();
    });

    // Create the local channel
    this.localChannel = this.ari.Channel();
    this.localChannel.on('StasisStart', (event, chan) => {
        this.bridge.addChannel({ channel: chan.id });
    });
    this.localChannel.on('StasisEnd', (event, chan) => {
        this.close();
    });

    // Originate the local channel
    try {
        await this.localChannel.originate({
            endpoint: this.options.dialstring,
            formats: this.options.format,
            app: "externalMedia",
        });
    } catch (error) {
        console.error("Error originating local channel:", error);
        this.close();
        return;
    }

    // Create the external channel
    this.externalChannel = this.ari.Channel();
    this.externalChannel.on('StasisStart', (event, chan) => {
        // Add the external channel to the bridge
        this.bridge.addChannel({ channel: chan.id });
        // Snoop on the local channel to start receiving audio
        this.localChannel.snoopChannel({ spy: this.externalChannel.id }, (err) => {
            if (err) {
                console.error("Error snooping channel:", err);
                this.close();
            }
        });
    });
    this.externalChannel.on('StasisEnd', (event, chan) => {
        this.close();
    });

    // Start external media
    try {
        let resp = await this.externalChannel.externalMedia({
            app: "externalMedia",
            external_host: this.options.listenServer,
            format: this.options.format
        });
        this.emit('ready');
    } catch (error) {
        console.error("Error starting external media:", error);
        this.close();
    }
}

}

module.exports.AriController = AriController;

In this code:

  • The SnoopChannel application is used to listen in on the localChannel and transmit the audio to the externalChannel.
  • The externalChannel is configured to receive external media from a specified host (presumably your transcription service).

Best Regard
Danish Hafeez | QA Assistant
ICTInnovations

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.