import { SOUNDS_BASE_URL } from './constants.js';
import { NotSupportedError, InvalidArgumentError, InvalidStateError } from './errors/index.js';
import Log from './log.js';

const DEFAULT_TEST_SOUND_URL = `${SOUNDS_BASE_URL}/outgoing.mp3`;
/**
 * A smart collection containing a Set of active output devices.
 */
class OutputDeviceCollection {
    /**
     * @internal
     */
    constructor(_name, _availableDevices, _beforeChange, _isSupported) {
        this._name = _name;
        this._availableDevices = _availableDevices;
        this._beforeChange = _beforeChange;
        this._isSupported = _isSupported;
        /**
         * The currently active output devices.
         */
        this._activeDevices = new Set();
        /**
         * An instance of Logger to use.
         */
        this._log = new Log('OutputDeviceCollection');
    }
    /**
     * Delete a device from the collection. If no devices remain, the 'default'
     * device will be added as the sole device. If no `default` device exists,
     * the first available device will be used.
     * @param device - The device to delete from the collection
     * @returns whether the device was present before it was deleted
     */
    delete(device) {
        this._log.debug('.delete', device);
        const wasDeleted = !!(this._activeDevices.delete(device));
        const defaultDevice = this._availableDevices.get('default')
            || Array.from(this._availableDevices.values())[0];
        if (!this._activeDevices.size && defaultDevice) {
            this._activeDevices.add(defaultDevice);
        }
        // Call _beforeChange so that the implementation can react when a device is
        // removed or lost.
        const deviceIds = Array.from(this._activeDevices.values()).map(deviceInfo => deviceInfo.deviceId);
        this._beforeChange(this._name, deviceIds);
        return !!wasDeleted;
    }
    /**
     * Get the current set of devices.
     */
    get() {
        return this._activeDevices;
    }
    /**
     * Replace the current set of devices with a new set of devices.
     * @param deviceIdOrIds - An ID or array of IDs of devices to replace the existing devices with.
     * @returns Rejects if this feature is not supported, any of the supplied IDs are not found,
     * or no IDs are passed.
     */
    set(deviceIdOrIds) {
        this._log.debug('.set', deviceIdOrIds);
        if (!this._isSupported) {
            return Promise.reject(new NotSupportedError('This browser does not support audio output selection'));
        }
        const deviceIds = Array.isArray(deviceIdOrIds) ? deviceIdOrIds : [deviceIdOrIds];
        if (!deviceIds.length) {
            return Promise.reject(new InvalidArgumentError('Must specify at least one device to set'));
        }
        const missingIds = [];
        const devices = deviceIds.map((id) => {
            const device = this._availableDevices.get(id);
            if (!device) {
                missingIds.push(id);
            }
            return device;
        });
        if (missingIds.length) {
            return Promise.reject(new InvalidArgumentError(`Devices not found: ${missingIds.join(', ')}`));
        }
        return new Promise(resolve => {
            resolve(this._beforeChange(this._name, deviceIds));
        }).then(() => {
            this._activeDevices.clear();
            devices.forEach(this._activeDevices.add, this._activeDevices);
        });
    }
    /**
     * Test the devices by playing audio through them.
     * @param [soundUrl] - An optional URL. If none is specified, we will
     *   play a default test tone.
     * @returns Resolves with the result of the underlying HTMLAudioElements' play() calls.
     */
    test(soundUrl = DEFAULT_TEST_SOUND_URL) {
        if (!this._isSupported) {
            return Promise.reject(new NotSupportedError('This browser does not support audio output selection'));
        }
        if (!this._activeDevices.size) {
            return Promise.reject(new InvalidStateError('No active output devices to test'));
        }
        return Promise.all(Array.from(this._activeDevices).map((device) => {
            let el;
            // (rrowland) We need to wait for the oncanplay event because of a regression introduced
            // in Chrome M72: https://bugs.chromium.org/p/chromium/issues/detail?id=930876
            return new Promise((resolve) => {
                el = new Audio(soundUrl);
                el.oncanplay = resolve;
            }).then(() => el.setSinkId(device.deviceId).then(() => el.play()));
        }));
    }
}

export { OutputDeviceCollection as default };
//# sourceMappingURL=data:application/json;charset=utf-8;base64, 
