Añada contenido online a su aplicación

Puede añadir contenido online a su aplicación MauiKit de sitios web que ofrecen acceso API.

La API REST es un servicio web ofrecido por sitios online, como por ejemplo RadioBrowser, YouTube, Unsplash y numerosos otros. Por lo general regresan la información en formato JSON, aunque también pueden regresar archivos XML o csv.

RadioBrowser

En este ejemplo se realiza búsqueda de radios en RadioBrowser, mostrando el contenido en la aplicación.

Ejemplo de búsqueda de término "brava" (abrir en Firefox):

https://nl1.api.radio-browser.info/json/stations/search?limit=999&name=brava&hidebroken=true&order=clickcount&reverse=true

Formato de respuesta JSON:

0:
    changeuuid: "be0ac639-9f24-413d-88e6-55c49468ae7c"
    stationuuid: "a4d441bf-b23d-40b2-b2cd-bb8022e6b033"
    serveruuid: "null"
    name: "Brava Radio 103.8 FM Jakarta"
    url: "http://stream.radiojar.com/5k7t0rq3bnzuv"
    url_resolved: "http://n07.radiojar.com/5k7t0rq3bnzuv?rj-ttl=5&rj-tok=AAABjFrGaFUAbQLObJQfi4s_eg"
    homepage: "https://bravaradio.com/"
    favicon: "https://bravaradio.com/favicon.ico"
    ...
1:
    changeuuid: "cbd66e0e-9275-45b5-bf30-0b184ca6ed8b"
    stationuuid: "8d7c5c53-26b4-4770-b3a7-77d2677567e2"
    serveruuid: "null"
    name: "Ke Brava"
    url: "https://stream.zenolive.com/59947b1wfuquv"
    url_resolved: "https://stream-160.zeno.fm/59947b1wfuquv?zs=uu-F3E8-R1WQS2gf2sNutw"
    homepage: "https://kebrava.blogspot.com/"
    favicon: "https://kebrava.blogspot.com/"
    ...

Una vez obtenido el objeto se accederá:

obj[i].changeuuid
obj[i].stationuuid
obj[i].serveruuid
obj[i].name
obj[i].url
obj[i].url_resolved
obj[i].homepage
obj[i].favicon
stationsModel.append({"name": obj[i].name,"url": obj[i].url})

Añade el siguiente código a una nueva aplicación MauiKit creada con el asistente de KDevelop.

1. Añade a main.qml:

// main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15
import org.mauikit.controls 1.3 as Maui

Maui.ApplicationWindow
{
    id: root

    ListModel { id: stationsModel }

    Maui.SideBarView
    {
        anchors.fill: parent

        sideBarContent: Maui.Page
        {
            Maui.Theme.colorSet: Maui.Theme.Window
            anchors.fill: parent

            headBar.visible: false

            ListModel {
            id: mainMenuModel
                ListElement { name: "Search" ; description: "Find a radio station" ; icon: "search" }
                ListElement { name: "Genre" ; description: "Music category" ; icon: "view-media-genre" }
                ListElement { name: "Language" ; description: "Search for a station in any language" ; icon: "languages" }
            }

            Maui.ListBrowser {
                id: menuSideBar

                anchors.fill: parent
                anchors.margins: 5

                horizontalScrollBarPolicy: ScrollBar.AlwaysOff
                verticalScrollBarPolicy: ScrollBar.AlwaysOff

                currentIndex: 0

                spacing: 5

                model: mainMenuModel

                delegate: Maui.ListBrowserDelegate {
                    width: ListView.view.width
                    height: 60
                    label1.text: name
                    label2.text: description

                    iconSource: icon

                    onClicked: {
                        switch (index) {
                            case 0: {
                                menuSideBar.currentIndex = index
                                stackView.push("qrc:/Search.qml")
                                return
                            }
                            case 1: {
                                menuSideBar.currentIndex = index
                                //stackView.push("qrc:/Page2.qml")
                                return
                            }
                            case 2: {
                                menuSideBar.currentIndex = index
                                //stackView.push("qrc:/Page3.qml")
                                return
                            }
                        }
                    }
                }
            }
        }

        Maui.Page
        {
            anchors.fill: parent

            headBar.visible: false

            Component.onCompleted: {
                stackView.push("qrc:/Search.qml")
            }

            StackView {
                id: stackView
                anchors.fill: parent
            }
        }
    }
}

2. Añade una nueva paǵina (KDevelop > File > New) llamada Search.qml:

// Search.qml

import QtQuick 2.15
import QtQuick.Controls 2.15
import org.mauikit.controls 1.3 as Maui

Maui.Page {
    id: searchPage

    showCSDControls: true

    headBar.background: Rectangle {
        anchors.fill: parent
        Maui.Theme.inherit: false
        Maui.Theme.colorSet: Maui.Theme.View
        color: Maui.Theme.backgroundColor
    }

    headBar.middleContent: Maui.SearchField {
        anchors.horizontalCenter: parent.horizontalCenter
        onAccepted: {
            stationsModel.clear()
            search(text)
        }
    }

    function search(query) {

        // RADIO BROWSER

        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
                print('HEADERS_RECEIVED')
            } else if(xhr.readyState === XMLHttpRequest.DONE) {
                print('DONE');
                var obj = JSON.parse(xhr.responseText.toString());

                for(var i=0; i<obj.length; i++) {
                    stationsModel.append({"changeuuid": obj[i].changeuuid,"stationuuid": obj[i].stationuuid, "serveruuid": obj[i].serveruuid,"name": obj[i].name,"url": obj[i].url,"url_resolved": obj[i].url_resolved,"homepage": obj[i].homepage,"favicon": obj[i].favicon,"tags": obj[i].tags,"country": obj[i].country, "countrycode": obj[i].countrycode, "iso_3166_2": obj[i].iso_3166_2,"state": obj[i].state, "language": obj[i].language, "languagecodes": obj[i].languagecodecs, "votes": obj[i].votes,"lastchangetime": obj[i].lastchangetime,"lastchangetime_iso8601": obj[i].lastchangetime_iso8601,"codec": obj[i].codec,"bitrate": obj[i].bitrate,"hls": obj[i].hls,"lastcheckok": obj[i].lastcheckok,"lastchecktime": obj[i].lastchecktime,"lastchecktime_iso8601": obj[i].lastchecktime_iso8601,"lastcheckoktime": obj[i].lastcheckoktime,"lastcheckoktime_iso8601": obj[i].lastcheckoktime_iso8601,"lastlocalchecktime": obj[i].lastlocalchecktime,"lastlocalchecktime_iso8601": obj[i].lastlocalchecktime_iso8601,"clicktimestamp": obj[i].clicktimestamp,"clicktimestamp_iso8601": obj[i].clicktimestamp_iso8601,"clickcount": obj[i].clickcount,"clicktrend": obj[i].clicktrend,"ssl_error": obj[i].ssl_error,"geo_lat	null": obj[i].geo_latnull,"geo_long": obj[i].geo_long, "has_extended_info": obj[i].has_extended_info})
                }
            }
        }
        xhr.open("GET", "https://nl1.api.radio-browser.info/json/stations/search?limit=999&name=" + query + "&hidebroken=true&order=clickcount&reverse=true");
        xhr.send();
    }

    Maui.ListBrowser {
        anchors.fill: parent
        anchors.margins: 20

        horizontalScrollBarPolicy: ScrollBar.AsNeeded
        verticalScrollBarPolicy: ScrollBar.AsNeeded

        spacing: 10

        model: stationsModel

        delegate: Rectangle {
            color: "transparent"
            width: ListView.view.width
            height: 80
            Maui.SwipeBrowserDelegate
            {
                anchors.fill: parent
                label1.text: name
                label2.text: tags
                iconSource: favicon
                iconSizeHint: Maui.Style.iconSizes.medium

                quickActions: [
                    Action
                    {
                        icon.name: "love"
                    },

                    Action
                    {
                        icon.name: "documentinfo"
                    }
                ]
            }
        }
    }
}

3. Añade Search.qml al archivo de recursos qml.qrc:

<RCC>
    <qresource prefix="/">
        <file>main.qml</file>
        <file>Search.qml</file>
    </qresource>
</RCC>

Solicitudes API

// Búsqueda
https://at1.api.radio-browser.info/json/stations/search?limit=10&name=brava&hidebroken=true&order=clickcount&reverse=true

// Estaciones por país:
https://at1.api.radio-browser.info/json/stations/search?limit=10&countrycode=US&hidebroken=true&order=clickcount&reverse=true

// Estaciones por idioma:
https://at1.api.radio-browser.info/json/stations/search?limit=10&language=spanish&hidebroken=true&order=clickcount&reverse=true

// Estaciones por tag:
https://at1.api.radio-browser.info/json/stations/search?limit=10&tagList=pop&hidebroken=true&order=clickcount&reverse=true

YouTube

Para acceder a YouTube es necesario registrar una clave API. Siga estos sencillos pasos para obtenerla:

Ejemplo de búsqueda de término "madrid" (abrir en Firefox). Es necesario añadir al final la clave API:

https://www.googleapis.com/youtube/v3/search?part=snippet&q=madrid&key=

Formato de respuesta JSON:

kind: "youtube#searchListResponse"
etag: "1FYjzINNJH9GjlwtsKREdJjDKaM"
nextPageToken: "CAUQAA"
regionCode: "ES"
pageInfo:
    totalResults: 1000000
    resultsPerPage: 5
items:
    [0]
        kind: "youtube#searchResult"
        etag: "gNbO2BDX38K5ztPi6vd5Oas2itQ"
        id:
            kind: "youtube#video"
            videoId: "ka5VtsbGl8E"
        snippet:
            publishedAt: "2023-12-09T22:20:09Z"
            channelId: "UCWV3obpZVGgJ3j9FVhEjF2Q"
            title: "Real Betis 1-1 Real Madrid | HIGHLIGHTS | LaLiga 2023/24"
            description: "Real Madrid drew at the Benito Villamarín on LaLiga matchday 16. Ancelotti's side took the lead through Bellingham, who has ..."
            thumbnails:
                default:
                    url: "https://i.ytimg.com/vi/ka5VtsbGl8E/default.jpg"
                    width: 120
                    ...
            publishTime: "2023-12-09T22:20:09Z" 
    [1]
        kind: "youtube#searchResult"
        etag: "yQ8FFqjetMcHVlSnYEyCQljo8co"
        id:
            kind: "youtube#video"
            videoId: "AfvnWbtRPiE"
        snippet: {
            publishedAt: "2023-12-01T05:00:07Z"
            channelId: "UCTeE0q8xCsbCBL4npUWDPFg"
            title: "Juhn - Madrid [Video Oficial]"
            description: "Juhn \"Madrid\", performing video (c) 2023 JSG Records (Juhn Music, S by Santana and Ganda LLC) Dale PLAY en todas las ..."
            thumbnails:
                default:
                url: "https://i.ytimg.com/vi/AfvnWbtRPiE/default.jpg"
                width: 120
                ...
            publishTime: "2023-12-01T05:00:07Z"
    [2]
    ...

Una vez obtenido el objeto se accederá:

obj.nextPageToken
obj.items[i].id.videoId
obj.items[i].snippet.title
obj.items[i].snippet.description
obj.items[i].snippet.thumbnails.default.url
videoModel.append({"videoId": obj.items[i].id.videoId,"title": obj.items[i].snippet.title})

Añade el siguiente código a una nueva aplicación MauiKit:

1. Añade a main.qml (incluya una clave API en apiKeyYouTube):

property string apiKeyYouTube: ""
// main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15
import org.mauikit.controls 1.3 as Maui

Maui.ApplicationWindow
{
    id: root

    property string apiKeyYouTube: ""

    ListModel { id: videoModel }

    Maui.SideBarView
    {
        anchors.fill: parent

        sideBarContent: Maui.Page
        {
            Maui.Theme.colorSet: Maui.Theme.Window
            anchors.fill: parent

            headBar.visible: false

            ListModel {
            id: mainMenuModel
                ListElement { name: "Search" ; description: "Find youtube videos" ; icon: "search" }
                ListElement { name: "Channels" ; description: "Favorite channels" ; icon: "view-media-favorite" }
                ListElement { name: "Playlists" ; description: "Save a playlist" ; icon: "view-media-playlist" }
            }

            Maui.ListBrowser {
                id: menuSideBar

                anchors.fill: parent
                anchors.margins: 5

                horizontalScrollBarPolicy: ScrollBar.AlwaysOff
                verticalScrollBarPolicy: ScrollBar.AlwaysOff

                currentIndex: 0

                spacing: 5

                model: mainMenuModel

                delegate: Maui.ListBrowserDelegate {
                    width: ListView.view.width
                    height: 60
                    label1.text: name
                    label2.text: description

                    iconSource: icon

                    onClicked: {
                        switch (index) {
                            case 0: {
                                menuSideBar.currentIndex = index
                                stackView.push("qrc:/Search.qml")
                                return
                            }
                            case 1: {
                                menuSideBar.currentIndex = index
                                //stackView.push("qrc:/Page2.qml")
                                return
                            }
                            case 2: {
                                menuSideBar.currentIndex = index
                                //stackView.push("qrc:/Page3.qml")
                                return
                            }
                        }
                    }
                }
            }
        }

        Maui.Page
        {
            anchors.fill: parent

            headBar.visible: false

            Component.onCompleted: {
                stackView.push("qrc:/Search.qml")
            }

            StackView {
                id: stackView
                anchors.fill: parent
            }
        }
    }
}

2. Añade una nueva paǵina (KDevelop > File > New) llamada Search.qml:

import QtQuick 2.15
import QtQuick.Controls 2.15
import org.mauikit.controls 1.3 as Maui

Maui.Page {
    id: searchPage

    showCSDControls: true

    headBar.background: Rectangle {
        anchors.fill: parent
        Maui.Theme.inherit: false
        Maui.Theme.colorSet: Maui.Theme.View
        color: Maui.Theme.backgroundColor
    }

    headBar.middleContent: Maui.SearchField {
        anchors.horizontalCenter: parent.horizontalCenter
        onAccepted: {
            videoModel.clear()
            search(text)
        }
    }

    function search(query) {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
                print('HEADERS_RECEIVED')
            } else if(xhr.readyState === XMLHttpRequest.DONE) {
                print('DONE');
                var obj = JSON.parse(xhr.responseText.toString());

                for(var i=0; i<obj.items.length; i++) {
                    videoModel.append({"videoId": obj.items[i].id.videoId,"title": obj.items[i].snippet.title,"thumbnailUrl": obj.items[i].snippet.thumbnails.default.url,"description": obj.items[i].snippet.description})
                }
            }
        }
        xhr.open("GET", "https://www.googleapis.com/youtube/v3/search?part=snippet&q=" + query + "&key=" + apiKeyYouTube);
        xhr.send();
    }

    Maui.ListBrowser {
        anchors.fill: parent
        anchors.margins: 20

        horizontalScrollBarPolicy: ScrollBar.AsNeeded
        verticalScrollBarPolicy: ScrollBar.AsNeeded

        spacing: 10

        model: videoModel

        delegate: Rectangle {
            color: "transparent"
            width: ListView.view.width
            height: 80
            Maui.SwipeBrowserDelegate
            {
                anchors.fill: parent
                label1.text: title
                label2.text: description
                iconSource: thumbnailUrl
                iconSizeHint: Maui.Style.iconSizes.medium

                quickActions: [
                    Action
                    {
                        icon.name: "love"
                    },

                    Action
                    {
                        icon.name: "documentinfo"
                    }
                ]
            }
        }
    }
}

3. Añade Search.qml al archivo de recursos qml.qrc:

<RCC>
    <qresource prefix="/">
        <file>main.qml</file>
        <file>Search.qml</file>
    </qresource>
</RCC>

Solicitudes API

// 1 Búsqueda:

// Primera página:
"https://www.googleapis.com/youtube/v3/search?part=snippet&q=" + query + "&maxResults=" + maxResults + "&key=" + apiKeyYouTube

// Páginas siguientes:
"https://www.googleapis.com/youtube/v3/search?pageToken=" + nextPageToken + "&part=snippet&q=" + query + "&maxResults=" + maxResults + "&key=" + apiKeyYouTube

// 2 Playlists de un canal:
"https://www.googleapis.com/youtube/v3/playlists?part=snippet&channelId=" + idChannel + "&maxResults=" + maxResults + "&key=" + apiKeyYouTube

// 3 Contenido de una playlist:

// Primera página:
"https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=" + playlistId + "&maxResults=" + maxResults + "&key=" + apiKeyYouTube

// Páginas siguientes:
"https://youtube.googleapis.com/youtube/v3/playlistItems?pageToken=" + nextPageToken + "&part=snippet&playlistId=" + playlistId + "&maxResults=" + maxResults + "&key=" + apiKeyYouTube

// 4 Contenido de un canal:

// Obtener playlist de subidas (info de canal):
"https://www.googleapis.com/youtube/v3/channels?id=" + channelId + "&key=" + apiKeyYouTube + "&part=snippet,contentDetails,statistics"

// Obtener contenido de la playlist de subidas (videos del canal):

// Primera página:
"https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=" + uploadsPlaylistId + "&maxResults=" + maxResults + "&key=" + apiKeyYouTube

// Páginas siguientes:
"https://youtube.googleapis.com/youtube/v3/playlistItems?pageToken=" + nextPageToken + "&part=snippet&playlistId=" + uploadsPlaylistId + "&maxResults=" + maxResults + "&key=" + apiKeyYouTube

Last updated