v0.9.390
This commit is contained in:
@@ -234,11 +234,15 @@ Work is in progress...
|
||||
|
||||
---
|
||||
## Version history
|
||||
### v0.9.390
|
||||
- updated the VU meter algorithms - shamelessly borrowed from @schreibfaul1, ([thanks a lot!](https://github.com/schreibfaul1/ESP32-audioI2S/blob/1296374fc513a6d6bfaa3b1ca08f6ba938b18d99/src/Audio.cpp#L5030))
|
||||
- fixed the magic error "HSPI" redefined.
|
||||
|
||||
### v0.9.380
|
||||
- fixed compilation error for ESP32 cores >= 3.1.0
|
||||
- fixed freezing error with incorrectly configured RTC module
|
||||
- [www|uart|telnet] new command `mode` - change SD/WEB mode. (0 - WEB, 1 - SD, 2 - Toggle)
|
||||
example: http://<ipaddr>/?mode=2
|
||||
example: http://\<ipaddress\>/?mode=2
|
||||
|
||||
#### v0.9.375
|
||||
- fixed the issue with saving settings for TIMEZONE.
|
||||
|
||||
@@ -2335,11 +2335,6 @@ bool Audio::pauseResume() {
|
||||
bool Audio::playChunk() {
|
||||
// If we've got data, try and pump it out..
|
||||
int16_t sample[2];
|
||||
/* VU Meter ************************************************************************************************************/
|
||||
/* По мотивам https://github.com/schreibfaul1/ESP32-audioI2S/pull/170/commits/6cce84217e5bc8f2f8925936affc84576932a29b */
|
||||
uint8_t maxl = 0, maxr = 0;
|
||||
uint8_t minl = 0xFF, minr = 0xFF;
|
||||
/************************************************************************************************************ VU Meter */
|
||||
if(getBitsPerSample() == 8) {
|
||||
if(getChannels() == 1) {
|
||||
while(m_validSamples) {
|
||||
@@ -2347,19 +2342,11 @@ bool Audio::playChunk() {
|
||||
uint8_t y = (m_outBuff[m_curSample] & 0xFF00) >> 8;
|
||||
sample[LEFTCHANNEL] = x;
|
||||
sample[RIGHTCHANNEL] = x;
|
||||
if(sample[LEFTCHANNEL] > maxl ) maxl = sample[LEFTCHANNEL];
|
||||
if(sample[RIGHTCHANNEL] > maxr ) maxr = sample[RIGHTCHANNEL];
|
||||
if(sample[LEFTCHANNEL] < minl ) minl = sample[LEFTCHANNEL];
|
||||
if(sample[RIGHTCHANNEL] < minr ) minr = sample[RIGHTCHANNEL];
|
||||
while(1) {
|
||||
if(playSample(sample)) break;
|
||||
} // Can't send?
|
||||
sample[LEFTCHANNEL] = y;
|
||||
sample[RIGHTCHANNEL] = y;
|
||||
if(sample[LEFTCHANNEL] > maxl ) maxl = sample[LEFTCHANNEL];
|
||||
if(sample[RIGHTCHANNEL] > maxr ) maxr = sample[RIGHTCHANNEL];
|
||||
if(sample[LEFTCHANNEL] < minl ) minl = sample[LEFTCHANNEL];
|
||||
if(sample[RIGHTCHANNEL] < minr ) minr = sample[RIGHTCHANNEL];
|
||||
while(1) {
|
||||
if(playSample(sample)) break;
|
||||
} // Can't send?
|
||||
@@ -2380,10 +2367,6 @@ bool Audio::playChunk() {
|
||||
sample[LEFTCHANNEL] = xy;
|
||||
sample[RIGHTCHANNEL] = xy;
|
||||
}
|
||||
if(sample[LEFTCHANNEL] > maxl ) maxl = sample[LEFTCHANNEL];
|
||||
if(sample[RIGHTCHANNEL] > maxr ) maxr = sample[RIGHTCHANNEL];
|
||||
if(sample[LEFTCHANNEL] < minl ) minl = sample[LEFTCHANNEL];
|
||||
if(sample[RIGHTCHANNEL] < minr ) minr = sample[RIGHTCHANNEL];
|
||||
while(1) {
|
||||
if(playSample(sample)) break;
|
||||
} // Can't send?
|
||||
@@ -2391,8 +2374,6 @@ bool Audio::playChunk() {
|
||||
m_curSample++;
|
||||
}
|
||||
}
|
||||
vuLeft = maxl - minl;
|
||||
vuRight = maxr - minr;
|
||||
m_curSample = 0;
|
||||
return true;
|
||||
}
|
||||
@@ -2401,10 +2382,6 @@ bool Audio::playChunk() {
|
||||
while(m_validSamples) {
|
||||
sample[LEFTCHANNEL] = m_outBuff[m_curSample];
|
||||
sample[RIGHTCHANNEL] = m_outBuff[m_curSample];
|
||||
if(sample[LEFTCHANNEL] > maxl ) maxl = sample[LEFTCHANNEL];
|
||||
if(sample[RIGHTCHANNEL] > maxr ) maxr = sample[RIGHTCHANNEL];
|
||||
if(sample[LEFTCHANNEL] < minl ) minl = sample[LEFTCHANNEL];
|
||||
if(sample[RIGHTCHANNEL] < minr ) minr = sample[RIGHTCHANNEL];
|
||||
if(!playSample(sample)) {
|
||||
log_e("can't send");
|
||||
return false;
|
||||
@@ -2425,17 +2402,11 @@ bool Audio::playChunk() {
|
||||
sample[LEFTCHANNEL] = xy;
|
||||
sample[RIGHTCHANNEL] = xy;
|
||||
}
|
||||
if(sample[LEFTCHANNEL] > maxl ) maxl = sample[LEFTCHANNEL];
|
||||
if(sample[RIGHTCHANNEL] > maxr ) maxr = sample[RIGHTCHANNEL];
|
||||
if(sample[LEFTCHANNEL] < minl ) minl = sample[LEFTCHANNEL];
|
||||
if(sample[RIGHTCHANNEL] < minr ) minr = sample[RIGHTCHANNEL];
|
||||
playSample(sample);
|
||||
m_validSamples--;
|
||||
m_curSample++;
|
||||
}
|
||||
}
|
||||
vuLeft = maxl - minl;
|
||||
vuRight = maxr - minr;
|
||||
m_curSample = 0;
|
||||
return true;
|
||||
}
|
||||
@@ -2444,10 +2415,86 @@ bool Audio::playChunk() {
|
||||
stopSong();
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Shamelessly borrowed from @schreibfaul1 https://github.com/schreibfaul1/ESP32-audioI2S/blob/1296374fc513a6d6bfaa3b1ca08f6ba938b18d99/src/Audio.cpp#L5030
|
||||
*/
|
||||
void Audio::_computeVUlevel(int16_t sample[2]) {
|
||||
if(!config.store.vumeter) return;
|
||||
static uint8_t sampleArray[2][4][8] = {0};
|
||||
static uint8_t cnt0 = 0, cnt1 = 0, cnt2 = 0, cnt3 = 0, cnt4 = 0;
|
||||
static bool f_vu = false;
|
||||
|
||||
auto avg = [&](uint8_t* sampArr) { // lambda, inner function, compute the average of 8 samples
|
||||
uint16_t av = 0;
|
||||
for(int i = 0; i < 8; i++) { av += sampArr[i]; }
|
||||
return av >> 3;
|
||||
};
|
||||
|
||||
auto largest = [&](uint8_t* sampArr) { // lambda, inner function, compute the largest of 8 samples
|
||||
uint16_t maxValue = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
if(maxValue < sampArr[i]) maxValue = sampArr[i];
|
||||
}
|
||||
return maxValue;
|
||||
};
|
||||
|
||||
if(cnt0 == 64) {
|
||||
cnt0 = 0;
|
||||
cnt1++;
|
||||
}
|
||||
if(cnt1 == 8) {
|
||||
cnt1 = 0;
|
||||
cnt2++;
|
||||
}
|
||||
if(cnt2 == 8) {
|
||||
cnt2 = 0;
|
||||
cnt3++;
|
||||
}
|
||||
if(cnt3 == 8) {
|
||||
cnt3 = 0;
|
||||
cnt4++;
|
||||
f_vu = true;
|
||||
}
|
||||
if(cnt4 == 8) { cnt4 = 0; }
|
||||
|
||||
if(!cnt0) { // store every 64th sample in the array[0]
|
||||
sampleArray[LEFTCHANNEL][0][cnt1] = abs(sample[LEFTCHANNEL] >> 7);
|
||||
sampleArray[RIGHTCHANNEL][0][cnt1] = abs(sample[RIGHTCHANNEL] >> 7);
|
||||
}
|
||||
if(!cnt1) { // store argest from 64 * 8 samples in the array[1]
|
||||
sampleArray[LEFTCHANNEL][1][cnt2] = largest(sampleArray[LEFTCHANNEL][0]);
|
||||
sampleArray[RIGHTCHANNEL][1][cnt2] = largest(sampleArray[RIGHTCHANNEL][0]);
|
||||
}
|
||||
if(!cnt2) { // store avg from 64 * 8 * 8 samples in the array[2]
|
||||
sampleArray[LEFTCHANNEL][2][cnt3] = largest(sampleArray[LEFTCHANNEL][1]);
|
||||
sampleArray[RIGHTCHANNEL][2][cnt3] = largest(sampleArray[RIGHTCHANNEL][1]);
|
||||
}
|
||||
if(!cnt3) { // store avg from 64 * 8 * 8 * 8 samples in the array[3]
|
||||
sampleArray[LEFTCHANNEL][3][cnt4] = avg(sampleArray[LEFTCHANNEL][2]);
|
||||
sampleArray[RIGHTCHANNEL][3][cnt4] = avg(sampleArray[RIGHTCHANNEL][2]);
|
||||
}
|
||||
if(f_vu) {
|
||||
f_vu = false;
|
||||
vuLeft = avg(sampleArray[LEFTCHANNEL][3]);
|
||||
if(vuLeft>config.vuThreshold) config.vuThreshold = vuLeft;
|
||||
vuRight = avg(sampleArray[RIGHTCHANNEL][3]);
|
||||
if(vuRight>config.vuThreshold) config.vuThreshold = vuRight;
|
||||
}
|
||||
cnt1++;
|
||||
}
|
||||
|
||||
uint16_t Audio::get_VUlevel(uint16_t dimension){
|
||||
if(!config.store.vumeter || config.vuThreshold==0) return 0;
|
||||
uint8_t L = map(vuLeft, config.vuThreshold, 0, 0, dimension);
|
||||
uint8_t R = map(vuRight, config.vuThreshold, 0, 0, dimension);
|
||||
return (L << 8) | R;
|
||||
}
|
||||
//---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
void Audio::loop() {
|
||||
if(!m_f_running) {
|
||||
vuLeft=0; vuRight=0;
|
||||
vTaskDelay(2);
|
||||
return;
|
||||
}
|
||||
@@ -4563,7 +4610,7 @@ bool Audio::playSample(int16_t sample[2]) {
|
||||
sample = IIR_filterChain1(sample);
|
||||
sample = IIR_filterChain2(sample);
|
||||
//-------------------------------------------
|
||||
|
||||
_computeVUlevel(sample);
|
||||
uint32_t s32 = Gain(sample); // vosample2lume;
|
||||
|
||||
if(m_f_internalDAC) {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#ifndef AUDIOBUFFER_MULTIPLIER2
|
||||
#define AUDIOBUFFER_MULTIPLIER2 8
|
||||
#endif
|
||||
|
||||
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
|
||||
#include "hal/gpio_ll.h"
|
||||
#endif
|
||||
@@ -211,7 +212,8 @@ public:
|
||||
/* VU METER */
|
||||
void setVUmeter() {};
|
||||
void getVUlevel() {};
|
||||
uint8_t vuLeft, vuRight;
|
||||
uint16_t get_VUlevel(uint16_t dimension);
|
||||
|
||||
bool eofHeader;
|
||||
esp_err_t i2s_mclk_pin_select(const uint8_t pin);
|
||||
uint32_t inBufferFilled(); // returns the number of stored bytes in the inputbuffer
|
||||
@@ -284,7 +286,7 @@ private:
|
||||
inline uint32_t streamavail(){ return _client ? _client->available() : 0;}
|
||||
void IIR_calculateCoefficients(int8_t G1, int8_t G2, int8_t G3);
|
||||
bool ts_parsePacket(uint8_t* packet, uint8_t* packetStart, uint8_t* packetLength);
|
||||
|
||||
void _computeVUlevel(int16_t sample[2]);
|
||||
// implement several function with respect to the index of string
|
||||
void trim(char *s) {
|
||||
//fb trim in place
|
||||
@@ -561,6 +563,7 @@ private:
|
||||
int16_t m_pidOfAAC;
|
||||
uint8_t m_packetBuff[m_tsPacketSize];
|
||||
int16_t m_pesDataLength = 0;
|
||||
uint16_t vuLeft, vuRight;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -1658,16 +1658,26 @@ void Audio::setVUmeter() {
|
||||
*
|
||||
* \warning This feature is only available with patches that support VU meter.
|
||||
*/
|
||||
void Audio::getVUlevel() {
|
||||
const uint8_t everyn = 4;
|
||||
void Audio::computeVUlevel() {
|
||||
if(!VS_PATCH_ENABLE) return;
|
||||
if(!_vuInitalized) return;
|
||||
static uint8_t cc = 0;
|
||||
cc++;
|
||||
if(!_vuInitalized || !config.store.vumeter || cc!=everyn) return;
|
||||
if(cc==everyn) cc=0;
|
||||
int16_t reg = read_register(SCI_AICTRL3);
|
||||
uint8_t rl = map((uint8_t)reg, 85, 92, 0, 255);
|
||||
uint8_t rr = map((uint8_t)(reg >> 8), 85, 92, 0, 255);
|
||||
//if(rl>30 || !isRunning()) vuLeft = rl;
|
||||
//if(rr>30 || !isRunning()) vuRight = rr;
|
||||
vuLeft = rl;
|
||||
vuRight = rr;
|
||||
vuLeft = map((uint8_t)(reg & 0x00FF), 85, 92, 0, 255);
|
||||
vuRight = map((uint8_t)(reg >> 8), 85, 92, 0, 255);
|
||||
if(vuLeft>config.vuThreshold) config.vuThreshold = vuLeft;
|
||||
if(vuRight>config.vuThreshold) config.vuThreshold=vuRight;
|
||||
}
|
||||
|
||||
uint16_t Audio::get_VUlevel(uint16_t dimension){
|
||||
if(!VS_PATCH_ENABLE) return 0;
|
||||
if(!_vuInitalized || !config.store.vumeter || config.vuThreshold==0) return 0;
|
||||
uint8_t L = map(vuLeft, config.vuThreshold, 0, 0, dimension);
|
||||
uint8_t R = map(vuRight, config.vuThreshold, 0, 0, dimension);
|
||||
return (L << 8) | R;
|
||||
}
|
||||
//---------------------------------------------------------------------------------------------------------------------
|
||||
void Audio::setConnectionTimeout(uint16_t timeout_ms, uint16_t timeout_ms_ssl){
|
||||
|
||||
@@ -236,6 +236,7 @@ private:
|
||||
|
||||
const char volumetable[22]={ 0,50,60,65,70,75,80,82,84,86,
|
||||
88,90,91,92,93,94,95,96,97,98,99,100}; //22 elements
|
||||
uint8_t vuLeft, vuRight;
|
||||
protected:
|
||||
inline void DCS_HIGH() {(dcs_pin&0x20) ? GPIO.out1_w1ts.data = 1 << (dcs_pin - 32) : GPIO.out_w1ts = 1 << dcs_pin;}
|
||||
inline void DCS_LOW() {(dcs_pin&0x20) ? GPIO.out1_w1tc.data = 1 << (dcs_pin - 32) : GPIO.out_w1tc = 1 << dcs_pin;}
|
||||
@@ -324,8 +325,8 @@ public:
|
||||
void forceMono(bool m) {} // TODO
|
||||
/* VU METER */
|
||||
void setVUmeter();
|
||||
void getVUlevel();
|
||||
uint8_t vuLeft, vuRight;
|
||||
uint16_t get_VUlevel(uint16_t dimension);
|
||||
void computeVUlevel();
|
||||
bool eofHeader;
|
||||
// implement several function with respect to the index of string
|
||||
bool startsWith (const char* base, const char* str) { return (strstr(base, str) - base) == 0;}
|
||||
|
||||
@@ -407,6 +407,7 @@ uint8_t Config::setLastSSID(uint8_t val) {
|
||||
}
|
||||
|
||||
void Config::setTitle(const char* title) {
|
||||
vuThreshold = 0;
|
||||
memset(config.station.title, 0, BUFLEN);
|
||||
strlcpy(config.station.title, title, BUFLEN);
|
||||
u8fix(config.station.title);
|
||||
|
||||
@@ -174,6 +174,7 @@ class Config {
|
||||
uint16_t sleepfor;
|
||||
uint32_t sdResumePos;
|
||||
bool emptyFS;
|
||||
uint16_t vuThreshold;
|
||||
public:
|
||||
Config() {};
|
||||
//void save();
|
||||
|
||||
@@ -452,6 +452,9 @@ void Display::loop() {
|
||||
}
|
||||
}
|
||||
dsp.loop();
|
||||
#if I2S_DOUT==255
|
||||
player.computeVUlevel();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Display::_setRSSI(int rssi) {
|
||||
|
||||
@@ -287,11 +287,12 @@ void NetServer::processQueue(){
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case GETSYSTEM: sprintf (wsbuf, "{\"sst\":%d,\"aif\":%d,\"vu\":%d,\"softr\":%d}",
|
||||
case GETSYSTEM: sprintf (wsbuf, "{\"sst\":%d,\"aif\":%d,\"vu\":%d,\"softr\":%d,\"vut\":%d}",
|
||||
config.store.smartstart != 2,
|
||||
config.store.audioinfo,
|
||||
config.store.vumeter,
|
||||
config.store.softapdelay);
|
||||
config.store.softapdelay,
|
||||
config.vuThreshold);
|
||||
break;
|
||||
case GETSCREEN: sprintf (wsbuf, "{\"flip\":%d,\"inv\":%d,\"nump\":%d,\"tsf\":%d,\"tsd\":%d,\"dspon\":%d,\"br\":%d,\"con\":%d}",
|
||||
config.store.flipscreen,
|
||||
|
||||
@@ -72,6 +72,7 @@ void ticks() {
|
||||
#ifdef USE_SD
|
||||
if(display.mode()!=SDCHANGE) player.sendCommand({PR_CHECKSD, 0});
|
||||
#endif
|
||||
player.sendCommand({PR_VUTONUS, 0});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef options_h
|
||||
#define options_h
|
||||
|
||||
#define YOVERSION "0.9.380"
|
||||
#define YOVERSION "0.9.390"
|
||||
|
||||
/*******************************************************
|
||||
DO NOT EDIT THIS FILE.
|
||||
@@ -467,12 +467,16 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti
|
||||
#define L10N_LANGUAGE EN
|
||||
#endif
|
||||
|
||||
#ifndef VSPI
|
||||
#define VSPI 3
|
||||
#endif
|
||||
#ifndef HSPI
|
||||
#define HSPI 1
|
||||
#ifdef VSPI
|
||||
#define VOOPSENb VSPI
|
||||
#else
|
||||
#define VOOPSENb 3
|
||||
#endif
|
||||
|
||||
#ifdef HSPI
|
||||
#define HOOPSENb HSPI
|
||||
#else
|
||||
#define HOOPSENb 2
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -157,6 +157,8 @@ void Player::loop() {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case PR_VUTONUS:
|
||||
if(config.vuThreshold>10) config.vuThreshold -=10;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@@ -186,6 +188,7 @@ void Player::_play(uint16_t stationId) {
|
||||
setError("");
|
||||
remoteStationName = false;
|
||||
config.setDspOn(1);
|
||||
config.vuThreshold = 0;
|
||||
//display.putRequest(PSTOP);
|
||||
if(config.getMode()!=PM_SDCARD) {
|
||||
display.putRequest(PSTOP);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#define PLERR_LN 64
|
||||
#define SET_PLAY_ERROR(...) {char buff[512 + 64]; sprintf(buff,__VA_ARGS__); setError(buff);}
|
||||
|
||||
enum playerRequestType_e : uint8_t { PR_PLAY = 1, PR_STOP = 2, PR_PREV = 3, PR_NEXT = 4, PR_VOL = 5, PR_CHECKSD = 6 };
|
||||
enum playerRequestType_e : uint8_t { PR_PLAY = 1, PR_STOP = 2, PR_PREV = 3, PR_NEXT = 4, PR_VOL = 5, PR_CHECKSD = 6, PR_VUTONUS = 7 };
|
||||
struct playerRequestParams_t
|
||||
{
|
||||
playerRequestType_e type;
|
||||
|
||||
@@ -294,9 +294,15 @@ void Nextion::drawVU(){
|
||||
//if(mode!=PLAYER) return;
|
||||
if(mode!=PLAYER && mode!=VOL) return;
|
||||
static uint8_t measL, measR;
|
||||
player.getVUlevel();
|
||||
uint8_t L = map(player.vuLeft, 0, 255, 0, 100);
|
||||
uint8_t R = map(player.vuRight, 0, 255, 0, 100);
|
||||
//player.getVUlevel();
|
||||
|
||||
uint16_t vulevel = player.get_VUlevel((uint16_t)100);
|
||||
|
||||
uint8_t L = (vulevel >> 8) & 0xFF;
|
||||
uint8_t R = vulevel & 0xFF;
|
||||
|
||||
//uint8_t L = map(player.vuLeft, 0, 255, 0, 100);
|
||||
//uint8_t R = map(player.vuRight, 0, 255, 0, 100);
|
||||
if(player.isRunning()){
|
||||
measL=(L<=measL)?measL-5:L;
|
||||
measR=(R<=measR)?measR-5:R;
|
||||
|
||||
@@ -284,21 +284,25 @@ void VuWidget::init(WidgetConfig wconf, VUBandsConfig bands, uint16_t vumaxcolor
|
||||
_canvas = new Canvas(_bands.width * 2 + _bands.space, _bands.height);
|
||||
}
|
||||
|
||||
|
||||
void VuWidget::_draw(){
|
||||
if(!_active || _locked) return;
|
||||
#if !defined(USE_NEXTION) && I2S_DOUT==255
|
||||
static uint8_t cc = 0;
|
||||
/* static uint8_t cc = 0;
|
||||
cc++;
|
||||
if(cc>0){
|
||||
player.getVUlevel();
|
||||
cc=0;
|
||||
}
|
||||
}*/
|
||||
#endif
|
||||
static uint16_t measL, measR;
|
||||
uint16_t bandColor;
|
||||
uint16_t dimension = _config.align?_bands.width:_bands.height;
|
||||
uint8_t L = map(player.vuLeft, 255, 0, 0, dimension);
|
||||
uint8_t R = map(player.vuRight, 255, 0, 0, dimension);
|
||||
uint16_t vulevel = player.get_VUlevel(dimension);
|
||||
|
||||
uint8_t L = (vulevel >> 8) & 0xFF;
|
||||
uint8_t R = vulevel & 0xFF;
|
||||
|
||||
bool played = player.isRunning();
|
||||
if(played){
|
||||
measL=(L>=measL)?measL + _bands.fadespeed:L;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "core/optionschecker.h"
|
||||
|
||||
#if DSP_HSPI || TS_HSPI || VS_HSPI
|
||||
SPIClass SPI2(HSPI);
|
||||
SPIClass SPI2(HOOPSENb);
|
||||
#endif
|
||||
|
||||
extern __attribute__((weak)) void yoradio_on_setup();
|
||||
|
||||
Reference in New Issue
Block a user