# TSGame JSBridge 接口文档 - 位置与媒体服务

**版本**: v1.9  
**更新时间**: 2025年7月13日  
**文档类型**: 接口规范文档 - 第三部分

## 文档说明

本文档是TSGame JSBridge接口规范文档的第三部分，专注于位置服务与媒体播放相关接口，包括GPS定位、音频播放控制等功能。

**适用平台**: Android / HarmonyOS  
**接口范围**: GPS定位服务、音频媒体播放

### 本文档包含的接口

| 接口名称 | 调用方向 | 主要功能 | 使用场景 |
|----------|----------|----------|----------|
| `startlocation` | WebView→App | 开始位置定位 | GPS定位服务 |
| `getlocationinfo` | WebView→App | 获取当前位置信息 | 位置信息查询 |
| `mediaTypeAudio` | WebView→App | 音频播放控制 | 音频媒体播放 |

---

## 1. 开始定位接口 (startlocation)

### 接口概述
当WebView需要开始GPS定位服务时，通过此接口启动位置定位功能。支持连续定位和单次定位两种模式，定位结果会通过`getlocationinfo`接口异步返回或自动回调到WebView。

### 工作流程
```mermaid
sequenceDiagram
    participant User as 用户
    participant WebView as WebView
    participant App as App
    participant LocationClient as 位置服务
    participant AMapSDK as 高德地图SDK
    participant GPS as GPS/网络定位
    
    User->>WebView: 触发定位需求
    WebView->>App: 调用startlocation接口
    App->>App: 解析定位类型参数
    App->>LocationClient: 初始化AMapLocationClient
    LocationClient->>AMapSDK: 配置定位选项
    AMapSDK->>GPS: 启动定位服务
    
    alt 连续定位模式
        GPS->>AMapSDK: 持续返回位置信息
        AMapSDK->>LocationClient: 定位回调(每3秒)
        LocationClient->>App: onLocationChanged事件
        App->>WebView: 调用getlocationinfo传递位置
    else 单次定位模式
        GPS->>AMapSDK: 返回最高精度位置
        AMapSDK->>LocationClient: 单次定位回调
        LocationClient->>App: onLocationChanged事件
        App->>App: 保存位置信息供后续查询
    end
```

### 参数规范

#### 调用格式
```javascript
WebViewJavascriptBridge.callHandler('startlocation', locationType, function(response) {
    console.log('定位服务已启动');
});
```

#### 参数说明
| 参数 | 类型 | 必需 | 说明 | 取值 |
|------|------|------|------|------|
| locationType | string | ✓ | 定位类型 | "1"=连续定位, "2"或其他=单次定位 |

#### 定位类型说明
| 类型值 | 定位模式 | 说明 | 适用场景 |
|--------|----------|------|----------|
| "1" | 连续定位 | 每3秒更新一次位置信息 | 导航、实时跟踪 |
| "2" | 单次定位 | 获取一次最高精度位置 | 位置标记、签到 |
| 其他值 | 单次定位 | 默认为单次定位模式 | 通用位置获取 |

### 接口调用示例

#### 启动连续定位
```javascript
// 启动连续定位，适用于导航场景
function startContinuousLocation() {
    WebViewJavascriptBridge.callHandler('startlocation', '1', function(response) {
        console.log('连续定位已启动，每3秒更新一次位置');
        
        // 连续定位会自动调用getlocationinfo回调
        // 需要注册位置更新处理函数
        window.onLocationUpdate = function(locationData) {
            updateUserPosition(locationData);
        };
    });
}
```

#### 启动单次定位
```javascript
// 启动单次定位，适用于签到场景
function startOnceLocation() {
    WebViewJavascriptBridge.callHandler('startlocation', '2', function(response) {
        console.log('单次定位已启动，正在获取最精确位置');
        
        // 等待3秒后查询位置信息
        setTimeout(() => {
            getLocationInfo();
        }, 3000);
    });
}

// 获取定位结果
function getLocationInfo() {
    WebViewJavascriptBridge.callHandler('getlocationinfo', '', function(locationJson) {
        if (locationJson) {
            const location = JSON.parse(locationJson);
            console.log('定位成功:', location);
            showLocationOnMap(location);
        } else {
            console.log('定位失败或未完成');
        }
    });
}
```

#### 智能定位管理器
```javascript
// 定位管理器
const LocationManager = {
    isLocating: false,
    locationType: 'once', // 'once' 或 'continuous'
    locationTimer: null,
    
    // 开始定位
    startLocation(type = 'once') {
        if (this.isLocating) {
            console.log('定位服务已在运行中');
            return;
        }
        
        this.locationType = type;
        this.isLocating = true;
        
        const typeParam = type === 'continuous' ? '1' : '2';
        
        WebViewJavascriptBridge.callHandler('startlocation', typeParam, (response) => {
            console.log(`${type}定位已启动`);
            
            if (type === 'continuous') {
                this.startLocationMonitoring();
            } else {
                this.checkLocationResult();
            }
        });
    },
    
    // 停止定位
    stopLocation() {
        this.isLocating = false;
        
        if (this.locationTimer) {
            clearInterval(this.locationTimer);
            this.locationTimer = null;
        }
        
        console.log('定位服务已停止');
    },
    
    // 监控连续定位
    startLocationMonitoring() {
        // 每4秒检查一次位置更新（略大于定位间隔3秒）
        this.locationTimer = setInterval(() => {
            this.getCurrentLocation((location) => {
                if (location) {
                    this.onLocationUpdate(location);
                }
            });
        }, 4000);
    },
    
    // 检查单次定位结果
    checkLocationResult() {
        // 等待3秒让定位完成
        setTimeout(() => {
            this.getCurrentLocation((location) => {
                this.isLocating = false;
                
                if (location) {
                    this.onLocationReceived(location);
                } else {
                    this.onLocationError('定位失败');
                }
            });
        }, 3000);
    },
    
    // 获取当前位置
    getCurrentLocation(callback) {
        WebViewJavascriptBridge.callHandler('getlocationinfo', '', (locationJson) => {
            if (locationJson) {
                try {
                    const location = JSON.parse(locationJson);
                    callback(location);
                } catch (e) {
                    console.error('位置数据解析失败:', e);
                    callback(null);
                }
            } else {
                callback(null);
            }
        });
    },
    
    // 位置更新回调（连续定位）
    onLocationUpdate(location) {
        console.log('位置更新:', location);
        // 更新地图显示
        updateMapPosition(location);
        // 触发位置变化事件
        this.triggerLocationChange(location);
    },
    
    // 位置获取回调（单次定位）
    onLocationReceived(location) {
        console.log('定位完成:', location);
        // 显示位置信息
        showLocationDetails(location);
        // 执行签到等操作
        performLocationBasedAction(location);
    },
    
    // 定位错误处理
    onLocationError(error) {
        console.error('定位错误:', error);
        showMessage('定位失败，请检查位置权限和网络连接');
    },
    
    // 触发位置变化事件
    triggerLocationChange(location) {
        const event = new CustomEvent('locationChanged', {
            detail: location
        });
        window.dispatchEvent(event);
    }
};

// 使用示例
LocationManager.startLocation('continuous'); // 启动连续定位
// LocationManager.startLocation('once');    // 启动单次定位

// 监听位置变化
window.addEventListener('locationChanged', function(event) {
    const location = event.detail;
    console.log('位置已更新:', location.address);
});
```

### HarmonyOS实现要点
- **接口名称**: `startlocation`
- **调用方向**: WebView → App
- **参数格式**: 字符串类型的定位类型（"1"=连续定位, "2"=单次定位）
- **权限要求**: 需要精确位置权限和网络权限
- **SDK依赖**: 使用高德地图SDK (AMapLocationClient)
- **定位模式**: 高精度定位模式 (Hight_Accuracy)
- **连续定位**: 3秒间隔的连续位置更新
- **单次定位**: 3秒内最高精度的一次定位
- **回调机制**: 通过AMapLocationListener.onLocationChanged接收位置更新

---

## 2. 获取位置信息接口 (getlocationinfo)

### 接口概述
当WebView需要获取当前设备的位置信息时，通过此接口查询最近一次定位的结果。必须先调用`startlocation`接口启动定位服务，才能获取到有效的位置数据。

### 工作流程
```mermaid
sequenceDiagram
    participant WebView as WebView
    participant App as App
    participant LocationCache as 位置缓存
    participant LocationData as 位置数据对象
    
    WebView->>App: 调用getlocationinfo接口
    App->>LocationCache: 检查缓存的位置信息
    
    alt 有位置数据
        LocationCache->>LocationData: 获取MaplocationInfo对象
        LocationData->>LocationData: 序列化为JSON
        LocationData->>App: 返回JSON字符串
        App->>WebView: 回调返回位置JSON
    else 无位置数据
        LocationCache->>App: 返回null
        App->>WebView: 回调返回null
    end
    
    WebView->>WebView: 解析位置JSON数据
    WebView->>WebView: 处理位置信息
```

### 参数规范

#### 调用格式
```javascript
WebViewJavascriptBridge.callHandler('getlocationinfo', '', function(locationJson) {
    if (locationJson) {
        const location = JSON.parse(locationJson);
        console.log('当前位置:', location);
    } else {
        console.log('暂无位置信息');
    }
});
```

#### 参数说明
| 参数 | 类型 | 必需 | 说明 | 取值 |
|------|------|------|------|------|
| data | string | ✓ | 固定传入空字符串 | "" |

#### 返回数据结构
| 字段 | 类型 | 说明 | 示例值 |
|------|------|------|-------|
| latitude | number | 纬度 | 39.908692 |
| longitude | number | 经度 | 116.397477 |
| address | string | 详细地址 | "北京市东城区正义路2号" |
| country | string | 国家 | "中国" |
| province | string | 省份 | "北京市" |
| city | string | 城市 | "北京市" |
| district | string | 城区 | "东城区" |
| street | string | 街道 | "正义路" |
| cityCode | string | 城市编码 | "010" |

### 接口调用示例

#### 基础位置获取
```javascript
// 获取当前位置信息
function getCurrentLocation() {
    WebViewJavascriptBridge.callHandler('getlocationinfo', '', function(locationJson) {
        if (locationJson) {
            try {
                const location = JSON.parse(locationJson);
                displayLocationInfo(location);
            } catch (e) {
                console.error('位置数据解析失败:', e);
            }
        } else {
            console.log('位置信息不可用，请先启动定位服务');
            startLocationFirst();
        }
    });
}

// 显示位置信息
function displayLocationInfo(location) {
    console.log('=== 当前位置信息 ===');
    console.log('纬度:', location.latitude);
    console.log('经度:', location.longitude); 
    console.log('地址:', location.address);
    console.log('城市:', location.city);
    console.log('区县:', location.district);
    
    // 更新页面显示
    updateLocationDisplay(location);
}

// 提示启动定位
function startLocationFirst() {
    if (confirm('需要先启动定位服务，是否立即启动？')) {
        WebViewJavascriptBridge.callHandler('startlocation', '2', function(response) {
            console.log('定位服务已启动，3秒后重新获取位置');
            setTimeout(getCurrentLocation, 3000);
        });
    }
}
```

#### 完整的位置服务流程
```javascript
// 位置服务管理器
const LocationService = {
    // 获取位置信息（带自动定位）
    getLocationWithAutoStart(callback) {
        // 先尝试获取已有的位置信息
        WebViewJavascriptBridge.callHandler('getlocationinfo', '', (locationJson) => {
            if (locationJson) {
                // 已有位置信息，直接返回
                try {
                    const location = JSON.parse(locationJson);
                    callback(null, location);
                } catch (e) {
                    callback('位置数据解析失败', null);
                }
            } else {
                // 没有位置信息，启动定位
                this.startLocationAndGet(callback);
            }
        });
    },
    
    // 启动定位并获取结果
    startLocationAndGet(callback) {
        console.log('正在启动定位服务...');
        
        WebViewJavascriptBridge.callHandler('startlocation', '2', (response) => {
            console.log('定位服务已启动，等待定位完成...');
            
            // 等待定位完成
            this.waitForLocation(callback, 0);
        });
    },
    
    // 等待定位完成
    waitForLocation(callback, attempts) {
        if (attempts >= 10) {
            callback('定位超时', null);
            return;
        }
        
        setTimeout(() => {
            WebViewJavascriptBridge.callHandler('getlocationinfo', '', (locationJson) => {
                if (locationJson) {
                    try {
                        const location = JSON.parse(locationJson);
                        callback(null, location);
                    } catch (e) {
                        callback('位置数据解析失败', null);
                    }
                } else {
                    // 继续等待
                    this.waitForLocation(callback, attempts + 1);
                }
            });
        }, 1000); // 每秒检查一次
    },
    
    // 验证位置数据完整性
    validateLocation(location) {
        const required = ['latitude', 'longitude'];
        const missing = required.filter(field => !location[field]);
        
        return {
            isValid: missing.length === 0,
            missing: missing,
            hasAddress: !!location.address
        };
    },
    
    // 格式化位置显示
    formatLocationDisplay(location) {
        const validation = this.validateLocation(location);
        
        if (!validation.isValid) {
            return '位置信息不完整';
        }
        
        // 构建显示文本
        let display = `${location.latitude.toFixed(6)}, ${location.longitude.toFixed(6)}`;
        
        if (validation.hasAddress) {
            display += `\n${location.address}`;
        }
        
        return display;
    }
};

// 使用示例
LocationService.getLocationWithAutoStart((error, location) => {
    if (error) {
        console.error('获取位置失败:', error);
        showMessage('定位失败: ' + error);
    } else {
        console.log('位置获取成功:', location);
        
        const validation = LocationService.validateLocation(location);
        if (validation.isValid) {
            const displayText = LocationService.formatLocationDisplay(location);
            showLocationInfo(displayText);
        } else {
            console.warn('位置数据不完整:', validation.missing);
        }
    }
});
```

### HarmonyOS实现要点
- **接口名称**: `getlocationinfo`
- **调用方向**: WebView → App
- **参数格式**: 空字符串
- **返回数据**: JSON字符串格式的位置信息对象
- **数据来源**: 由startlocation接口触发的定位结果
- **数据结构**: MaplocationInfo对象的JSON序列化
- **前置条件**: 需要先调用startlocation启动定位服务
- **数据字段**: 包含经纬度、地址、行政区划等完整位置信息
- **缓存机制**: App内存中保存最新一次定位结果

---

## 3. 音频播放接口 (mediaTypeAudio)

### 接口概述
当WebView需要播放音频文件时，通过此接口实现音频的播放控制。支持普通音频和历史音频两种类型，具有防重复播放机制，播放开始时会调用gameui_play_voice通知，播放完成后会自动通知WebView。

### 工作流程
```mermaid
sequenceDiagram
    participant User as 用户
    participant WebView as WebView
    participant App as App
    participant MediaPlayer as 媒体播放器
    participant AudioSystem as 音频系统
    participant Server as 音频服务器
    
    User->>WebView: 触发音频播放
    WebView->>App: 调用mediaTypeAudio接口
    App->>App: 解析音频参数(audiourl/type/user)
    App->>App: 检查防重复播放标志
    
    alt 可以播放音频
        App->>App: 验证音频URL格式
        App->>Server: 下载音频文件(如果是网络URL)
        Server->>App: 返回音频数据
        App->>MediaPlayer: 初始化播放器
        App->>WebView: 调用gameui_play_voice通知播放开始
        MediaPlayer->>AudioSystem: 开始播放音频
        
        Note over MediaPlayer,AudioSystem: 播放过程中
        MediaPlayer->>App: 播放状态回调
        App->>App: 更新播放标志状态
        
        alt 播放完成
            MediaPlayer->>App: 播放完成回调
            App->>WebView: 调用gameui_stop_voice通知
            App->>App: 重置播放状态
        else 播放错误
            MediaPlayer->>App: 播放错误回调
            App->>App: 重置播放状态
            App->>WebView: 错误通知
        end
        
    else 正在播放中
        App->>App: 添加到延时播放队列
        Note over App: 200ms后重试播放
    end
```

### 参数规范

#### 调用格式
```javascript
const audioData = {
    audiourl: "https://example.com/audio.mp3",
    type: "1", 
    user: "user_123"
};

WebViewJavascriptBridge.callHandler('mediaTypeAudio', JSON.stringify(audioData), function(response) {
    console.log('音频播放请求已发送');
});
```

#### 参数说明
| 参数 | 类型 | 必需 | 说明 | 示例值 |
|------|------|------|------|--------|
| audiourl | string | ✓ | 音频文件URL地址 | "https://cdn.example.com/sound.mp3" |
| type | string | ✓ | 音频类型 | "1"=普通音频, "2"=历史音频 |
| user | string | ✓ | 用户标识或动画位置 | "user_123", "avatar_position_1" |

#### 音频类型说明
| 类型值 | 播放模式 | 说明 | 适用场景 |
|--------|----------|------|----------|
| "1" | 普通音频 | 新音频播放，支持防重复机制 | 音效、提示音 |
| "2" | 历史音频 | 历史音频重播，无防重复限制 | 聊天记录回放 |

### 接口调用示例

#### 播放游戏音效
```javascript
// 播放游戏胜利音效
function playVictorySound() {
    const audioData = {
        audiourl: "https://game.example.com/sounds/victory.mp3",
        type: "1",
        user: "game_effect_victory"
    };
    
    WebViewJavascriptBridge.callHandler('mediaTypeAudio', JSON.stringify(audioData), function(response) {
        console.log('胜利音效播放中...');
    });
}

// 播放按钮点击音效
function playButtonClickSound() {
    const audioData = {
        audiourl: "https://game.example.com/sounds/button_click.mp3", 
        type: "1",
        user: "ui_button_click"
    };
    
    WebViewJavascriptBridge.callHandler('mediaTypeAudio', JSON.stringify(audioData), function(response) {
        console.log('按钮音效播放中...');
    });
}
```

#### 完整的音频管理系统
```javascript
// 音频播放管理器
const AudioManager = {
    playingAudios: new Map(), // 正在播放的音频
    audioQueue: [], // 音频播放队列
    isProcessingQueue: false,
    
    // 播放音频
    playAudio(audioUrl, options = {}) {
        const audioData = {
            audiourl: audioUrl,
            type: options.type || "1",
            user: options.userId || this.generateUserId()
        };
        
        // 添加到播放队列
        this.addToQueue(audioData, options.callback);
        
        // 处理队列
        this.processQueue();
        
        return audioData.user;
    },
    
    // 生成唯一用户ID
    generateUserId() {
        return "audio_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
    },
    
    // 添加到播放队列
    addToQueue(audioData, callback) {
        this.audioQueue.push({
            audioData: audioData,
            callback: callback,
            timestamp: Date.now()
        });
    },
    
    // 处理播放队列
    processQueue() {
        if (this.isProcessingQueue || this.audioQueue.length === 0) {
            return;
        }
        
        this.isProcessingQueue = true;
        
        const queueItem = this.audioQueue.shift();
        this.executePlayback(queueItem);
    },
    
    // 执行播放
    executePlayback(queueItem) {
        const { audioData, callback } = queueItem;
        
        // 记录播放状态
        this.playingAudios.set(audioData.user, {
            audioData: audioData,
            startTime: Date.now(),
            callback: callback
        });
        
        console.log('开始播放音频:', audioData.audiourl);
        
        WebViewJavascriptBridge.callHandler('mediaTypeAudio', JSON.stringify(audioData), (response) => {
            console.log('音频播放请求已发送:', audioData.user);
            
            if (callback) {
                callback(null, audioData.user);
            }
            
            // 继续处理下一个队列项
            setTimeout(() => {
                this.isProcessingQueue = false;
                this.processQueue();
            }, 200);
        });
    },
    
    // 处理播放开始
    onAudioStart(userId) {
        const playingInfo = this.playingAudios.get(userId);
        
        if (playingInfo) {
            console.log(`音频播放开始: ${userId}`);
            
            // 触发播放开始事件
            this.triggerAudioStartEvent(userId);
            
            // 调用回调
            if (playingInfo.callback) {
                playingInfo.callback(null, 'started');
            }
        } else {
            console.log(`收到播放开始通知，但未找到对应的音频记录: ${userId}`);
        }
    },
    
    // 处理播放完成
    onAudioComplete(userId) {
        const playingInfo = this.playingAudios.get(userId);
        
        if (playingInfo) {
            const duration = Date.now() - playingInfo.startTime;
            console.log(`音频播放完成: ${userId}, 耗时: ${duration}ms`);
            
            // 移除播放记录
            this.playingAudios.delete(userId);
            
            // 触发完成事件
            this.triggerAudioCompleteEvent(userId, duration);
            
            // 调用回调
            if (playingInfo.callback) {
                playingInfo.callback(null, 'completed');
            }
        }
    },
    
    // 触发音频开始事件
    triggerAudioStartEvent(userId) {
        const event = new CustomEvent('audioPlayStart', {
            detail: {
                userId: userId
            }
        });
        window.dispatchEvent(event);
    },
    
    // 触发音频完成事件
    triggerAudioCompleteEvent(userId, duration) {
        const event = new CustomEvent('audioPlayComplete', {
            detail: {
                userId: userId,
                duration: duration
            }
        });
        window.dispatchEvent(event);
    },
    
    // 停止所有音频
    stopAllAudio() {
        this.playingAudios.clear();
        this.audioQueue = [];
        this.isProcessingQueue = false;
        console.log('已停止所有音频播放');
    },
    
    // 获取播放状态
    getPlayingStatus() {
        return {
            playingCount: this.playingAudios.size,
            queueLength: this.audioQueue.length,
            isProcessing: this.isProcessingQueue
        };
    }
};

// 注册音频播放相关监听
if (typeof WebViewJavascriptBridge !== 'undefined') {
    // 注册音频播放开始通知
    WebViewJavascriptBridge.registerHandler('gameui_play_voice', function(userId) {
        console.log('收到音频播放开始通知:', userId);
        AudioManager.onAudioStart(userId);
    });
    
    // 注册音频播放完成通知
    WebViewJavascriptBridge.registerHandler('gameui_stop_voice', function(userId) {
        console.log('收到音频播放完成通知:', userId);
        AudioManager.onAudioComplete(userId);
    });
}

// 使用示例
const userId1 = AudioManager.playAudio('https://example.com/sound1.mp3', {
    type: '1',
    userId: 'background_music',
    callback: (error, result) => {
        if (error) {
            console.error('播放失败:', error);
        } else {
            console.log('播放结果:', result);
        }
    }
});

// 监听播放开始事件
window.addEventListener('audioPlayStart', function(event) {
    const { userId } = event.detail;
    console.log(`音频 ${userId} 开始播放`);
});

// 监听播放完成事件
window.addEventListener('audioPlayComplete', function(event) {
    const { userId, duration } = event.detail;
    console.log(`音频 ${userId} 播放完成，耗时 ${duration}ms`);
});
```

### HarmonyOS实现要点
- **接口名称**: `mediaTypeAudio`
- **调用方向**: WebView → App
- **参数格式**: JSON字符串，包含audiourl、type、user字段
- **音频支持**: 支持各种音频格式(MP3、WAV、AAC等)
- **防重复机制**: type为"1"时启用防重复播放检查
- **播放队列**: 正在播放时新音频延时200ms重试
- **开始通知**: 通过gameui_play_voice回调通知WebView播放开始
- **完成通知**: 通过gameui_stop_voice回调通知WebView播放完成
- **音量控制**: 受voicePlaying接口的全局音量开关影响
- **错误处理**: 包含URL验证、格式检查、播放状态管理

---

## 文档关联

本文档是TSGame JSBridge接口规范的第三部分，完整文档包括：

1. **用户认证与页面管理** - 登录、页面跳转、语音控制
2. **系统功能与交互** - 剪贴板、游戏状态、设备检测
3. **位置与媒体服务** (本文档) - GPS定位、音频播放

---

**文档维护**: TSGame开发团队  
**最后更新**: 2025年7月13日  
**版本**: v1.9  
**联系方式**: 技术支持组
