QML Tuner Example

This Example shows how to use the Tuner API from QML.

This Example shows how to use the Tuner API from QML.

First an AmFmTuner object is created. By default, the auto discovery is used to search for a plugin that implements QIviAmFmTunerBackendInterface. Depending on the selection of the band radio buttons the tuner band is selected.

AmFmTuner {
    id: tuner

    band: bandGroup.current.text === "AM" ? AmFmTuner.AMBand : AmFmTuner.FMBand

    onScanStarted: {
        console.log("A Station SCAN has been started")
    }

    onScanStopped: {
        console.log("A Station SCAN has been stopped")
    }
}

Station Information

In the left third of the UI we want to display information about the current radio station as well as providing some buttons to change the stations or start a scan through all stations.

ColumnLayout {

    RowLayout {
        Label { text: "Station:" }
        Label { text: tuner.station.stationName }
    }

    RowLayout {
        Label { text: "Frequency:" }
        Label { text: tuner.frequency }
    }

    RowLayout {
        Label { text: "Band:" }
        RadioButton { text: "AM"; exclusiveGroup: bandGroup; checked: tuner.band === AmFmTuner.AMBand }
        RadioButton { text: "FM"; exclusiveGroup: bandGroup; checked: tuner.band === AmFmTuner.FMBand }
    }

    GroupBox {
        title: "Band settings"
        ColumnLayout {
            RowLayout {
                Label { text: "Step Size:" }
                Label { text: tuner.stepSize }
            }

            RowLayout {
                Label { text: "Minimum Frequency:" }
                Label { text: tuner.minimumFrequency }
            }

            RowLayout {
                Label { text: "Maximum Frequency::" }
                Label { text: tuner.maximumFrequency }
            }
        }
    }

    ExclusiveGroup {
        id: bandGroup
    }

    RowLayout {
        Button {
            text: "<-"
            onClicked: tuner.seekDown()
        }

        Button {
            text: "<"
            onClicked: tuner.stepDown()
        }

        Button {
            text: "Scan"
            checkable: true
            onClicked: {
                if (checked)
                    tuner.startScan();
                else
                    tuner.stopScan();
            }
        }

        Button {
            text: ">"
            onClicked: tuner.stepUp()
        }

        Button {
            text: "->"
            onClicked: tuner.seekUp()
        }
    }

}

The station property of AmFmTuner exposes the station you are currently listening to, which can be empty as well, if the frequency property was manually changed to a frequency no station is broadcasting on.

Station List

The middle part of the UI shows a list of all the available radio stations. Every item of the list shows the name and the frequency of a station. By clicking on one of the list items, the current station will be changed to this station. On the right side of every station is a + button which can be used to save this station into the preset list.

ListView {
    spacing: 8
    clip: true

    width: 300
    Layout.fillHeight: true

    model: SearchAndBrowseModel {
        serviceObject: tuner.serviceObject
        contentType: "station"
    }

    delegate: Rectangle {
        width: ListView.view.width
        height: column.height
        color: "#efefef"

        MouseArea {
            anchors.fill: parent
            onClicked: {
                tuner.tune(model.item)
            }
        }

        Row {
            anchors.fill: parent
            Column {
                id: column
                width: parent.width * 9 / 10

                Text { text: "Name: " + model.item.stationName }
                Text { text: "Type: " + model.item.frequency }
            }

            Button {
                id: addButton
                text: "+"
                width: parent.width / 10
                height: parent.height

                onClicked: {
                    presetsModel.insert(0, model.item)
                }

                function checkExists() {
                    presetsModel.indexOf(model.item).then(function (index) {
                        addButton.enabled = (index === -1)
                    })
                }

                Component.onCompleted: {
                    checkExists()
                }

                Connections {
                    target: presetsModel
                    onCountChanged: addButton.checkExists()
                }
            }
        }
    }
}

To fill the ListView with all available stations, the SearchAndBrowseModel model is used. As the SearchAndBrowseModel is a generic model, it needs to know where the data should come from. This is done by passing the service object of the AmFmTuner to the model. The model will then use the QIviSearchAndBrowseModelInterface exposed by the same backend which is used by AmFmTuner. Because the tuner backend could expose multiple different lists, the contentType needs to be selected: in this case the contentType is set to station, which provides all available stations.

model: SearchAndBrowseModel {
    serviceObject: tuner.serviceObject
    contentType: "station"
}

To change the currently playing station the AmFmTuner::tune method is used by calling it in an onClicked handler

MouseArea {
    anchors.fill: parent
    onClicked: {
        tuner.tune(model.item)
    }
}

Preset List

The preset list occupies the right third of the UI and shows all favorite stations. This list is sorted and maintained by the user. A press on the + button of the station list will add a station to this list, the X button will remove the item and the arrow buttons can be used to change the order of the stations.

ListView {
    spacing: 8
    clip: true
    Layout.fillWidth: true

    model: SearchAndBrowseModel {
        id: presetsModel
        serviceObject: tuner.serviceObject
        contentType: "presets"
    }

    delegate: Rectangle {
        width: ListView.view.width
        height: column.height
        color: "#efefef"

        MouseArea {
            anchors.fill: parent
            onClicked: {
                tuner.tune(model.item)
            }
        }

        Row {
            anchors.fill: parent
            Column {
                id: column
                width: parent.width * 7 / 10

                Text { text: "Name: " + model.item.stationName }
                Text { text: "Type: " + model.item.frequency }
            }

            Button {
                text: "\u2227"
                width: parent.width / 10
                height: parent.height

                enabled: index > 0

                onClicked: {
                    presetsModel.move(index, index - 1)
                }
            }

            Button {
                text: "\u2228"
                width: parent.width / 10
                height: parent.height

                enabled: index < presetsModel.count -1

                onClicked: {
                    presetsModel.move(index, index + 1)
                }
            }

            Button {
                text: "X"
                width: parent.width / 10
                height: parent.height

                onClicked: {
                    presetsModel.remove(index)
                }
            }
        }
    }
}

Similar to the station list, the SearchAndBrowseModel is used as a model, but the contentType was changed to presets. For maintaining the list, the move and remove functions of SearchAndBrowseModel are used.

Favorite Button

The + button of the station list should be enabled if the station is not already part of the preset list. This is done by using the SearchAndBrowseModel::indexOf function which will search for the passed item and call the callback function passed as second argument with the result. Depending on whether the index is valid, the button will be enabled or disabled. This asynchronous approach is needed, as the preset list might be pretty big and the data might come from a different process which maintains the tuner state.

Button {
    id: addButton
    text: "+"
    width: parent.width / 10
    height: parent.height

    onClicked: {
        presetsModel.insert(0, model.item)
    }

    function checkExists() {
        presetsModel.indexOf(model.item).then(function (index) {
            addButton.enabled = (index === -1)
        })
    }

    Component.onCompleted: {
        checkExists()
    }

    Connections {
        target: presetsModel
        onCountChanged: addButton.checkExists()
    }
}

If not already part of the preset list, the station is added to the list by using the SearchAndBrowseModel::insert method, which is passed 0 as the first parameter to add it on top of the list.

Example project @ code.qt.io

© 2020 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.