微信分享去sdk

This commit is contained in:
joywayer
2026-06-30 17:40:54 +08:00
parent 04e702a99b
commit c1e51d2183
2 changed files with 198 additions and 146 deletions
+49 -32
View File
@@ -7,22 +7,22 @@
// docs/H5-Native-Contract.md §3.1 5mediaTypeAudio + §3.2 78 // docs/H5-Native-Contract.md §3.1 5mediaTypeAudio + §3.2 78
// gameui_play_voice / gameui_stop_voice // gameui_play_voice / gameui_stop_voice
// //
// ## // ##
// //
// daoqi msext NewRootVC.m / gameController.m voicePlayer / User_id // daoqi msext NewRootVC.m / gameController.m voicePlayer / User_id
// VC // VC `voicePlayer = [[AVAudioPlayer alloc]...]`
// ivar player
// `self.User_id` stopradio H5
// `gameui_stop_voice` payload ** user_id**
// H5
// //
// - `lastUserId`mediaTypeAudio user mediaTypeAudio // user player `currentUser`
// daoqi gameController.m:438 // - play player stop player
// `self.User_id=userid;` canbofang=NO // `onFinish(currentUser)` H5 `gameui_stop_voice( user)`
// - `gameui_stop_voice` payload `lastUserId` didFinishPlaying // - / `currentUser` " user"
// player daoqi stopradio self.User_id // onFinish
// gameui_stop_voice user** daoqi ** // - stop() onFinish daoqi
// - play player // stop
// ARC/ARC-like daoqi `voicePlayer = [[AVAudioPlayer alloc]...]`
//
// - stop **** onFinishdaoqi stop
// audioPlayerDidFinishPlaying stopradio
// //
import AVFoundation import AVFoundation
@@ -36,42 +36,57 @@ public final class VoicePlayer: NSObject, AVAudioPlayerDelegate {
private var player: AVAudioPlayer? private var player: AVAudioPlayer?
/// mediaTypeAudio user /// user `player` `player != nil`
/// daoqi self.User_id mediaTypeAudio /// `player == nil` nil play / didFinishPlaying / decodeError
public private(set) var lastUserId: String = "" /// onFinish `gameui_stop_voice` payload /
private var currentUser: String?
/// AVAudioPlayer didFinishPlaying / /// / /
/// mediaTypeAudio handler closure gameui_stop_voice /// mediaTypeAudio handler closure H5 `gameui_stop_voice`
/// payload `lastUserId` player user daoqi /// payload / player user user
public var onFinish: ((String) -> Void)? public var onFinish: ((String) -> Void)?
private override init() { super.init() } private override init() { super.init() }
/// mediaTypeAudio user**** /// wav player stop player
/// daoqi gameController.m:438voicePlaying=0 `self.User_id=userid` /// `onFinish( user)` H5 player
public func recordIncomingUser(_ user: String) { /// - Throws: AVAudioPlayer /
lastUserId = user public func play(_ wav: URL, user: String) throws {
} // 1. player "" H5
// delegate nil player stop / dealloc
// didFinishPlaying onFinish
if let old = player {
old.delegate = nil
old.stop()
player = nil
if let interrupted = currentUser {
currentUser = nil
Self.log.debug("interrupted user=\(interrupted, privacy: .public)")
onFinish?(interrupted)
} else {
currentUser = nil
}
}
/// wav player daoqi // 2. player
/// `recordIncomingUser(user)` lastUserId
/// - Throws: AVAudioPlayer /
public func play(_ wav: URL) throws {
let p = try AVAudioPlayer(contentsOf: wav) let p = try AVAudioPlayer(contentsOf: wav)
p.delegate = self p.delegate = self
guard p.prepareToPlay(), p.play() else { guard p.prepareToPlay(), p.play() else {
throw PlayerError.startFailed throw PlayerError.startFailed
} }
player = p player = p
Self.log.debug("play \(wav.lastPathComponent, privacy: .public) user=\(self.lastUserId, privacy: .public)") currentUser = user
Self.log.debug("play \(wav.lastPathComponent, privacy: .public) user=\(user, privacy: .public)")
} }
/// onFinish /// onFinish
/// 使 H5 BackGameData 退 /// 使 H5 BackGameData 退
/// didFinishPlaying /// didFinishPlaying
public func stop() { public func stop() {
player?.delegate = nil
player?.stop() player?.stop()
player = nil player = nil
currentUser = nil
} }
public enum PlayerError: Error, Sendable { public enum PlayerError: Error, Sendable {
@@ -110,8 +125,10 @@ public final class VoicePlayer: NSObject, AVAudioPlayerDelegate {
/// player self.player + onFinish /// player self.player + onFinish
private func handleFinish(playerIdentity: ObjectIdentifier, success: Bool) { private func handleFinish(playerIdentity: ObjectIdentifier, success: Bool) {
guard let current = player, ObjectIdentifier(current) == playerIdentity else { return } guard let current = player, ObjectIdentifier(current) == playerIdentity else { return }
let user = currentUser
player = nil player = nil
Self.log.debug("didFinish user=\(self.lastUserId, privacy: .public) success=\(success, privacy: .public)") currentUser = nil
onFinish?(lastUserId) Self.log.debug("didFinish user=\(user ?? "<nil>", privacy: .public) success=\(success, privacy: .public)")
if let user { onFinish?(user) }
} }
} }
+149 -114
View File
@@ -2,161 +2,196 @@
// WechatShare.swift // WechatShare.swift
// ylgamehall // ylgamehall
// //
// SharePlatform + shareLobby() // SharePlatform + + `weixin://` URL Scheme
// QQShare / DouyinShare ** OpenSDK API**
// //
// msext ViewController handler //
// - OpenSDK sendReq " plist + AppID "
// QQ / " URL Scheme"
// SDK +
// - OpenSDK WeChatSDK.register / WeChatAuth.authorize
// SDK WeChatManager
// - scene friend / timeline SharePlatform
//
// //
// share() daoqi msext `WechatShareManager.shareWithContent:` // ViewController handler
// gameController.m 2 type
// - type == "2" thumb = app icon
// - else thumb = app icon
// //
// shareLobby() daoqi msext `NewRootVC.m:471-534` 3 type // share() gameController.m2 type
// - type == "1" thumb = app icon msext // - type == "2" UIPasteboard.image
// sharelogo.png fallback app icon // - else UIPasteboard.string
// - type == "2" thumb = 40%
// - NSData + thumb = 40%
// //
// fire-and-forgetH5 sharesuccess FriendsShareHandler // shareLobby() NewRootVC.m3 type
// dispatch_after 1s completion(YES) // - type == "1"
// - type == "2"
// - UIPasteboard.image
//
// fire-and-forgetH5 sharesuccess FriendsShareHandler
//
// //
import Foundation import Foundation
import UIKit import UIKit
import os.log
@MainActor @MainActor
public final class WechatShare: SharePlatform { public final class WechatShare: SharePlatform {
public static let shared = WechatShare() public static let shared = WechatShare()
private static let log = Logger(subsystem: "ylgamehall", category: "WechatShare")
private static let wechatScheme = "weixin://"
public let name = "WeChat" public let name = "WeChat"
public var isInstalled: Bool { public var isInstalled: Bool {
WXApi.isWXAppInstalled() guard let url = URL(string: Self.wechatScheme) else { return false }
return UIApplication.shared.canOpenURL(url)
} }
public init() {} public init() {}
// MARK: - gameController.m
public func share(_ content: ShareContent, scene: ShareScene) { public func share(_ content: ShareContent, scene: ShareScene) {
guard WXApi.isWXAppInstalled() else { return } guard isInstalled else { return }
let wxScene: WeChatManager.ShareScene = (scene == .timeline) ? .timeline : .session if content.type == "2" {
let thumb = Self.appIconImage() shareScreenshot()
} else {
Task { @MainActor in shareLink(content)
do {
if content.type == "2" {
// msext WechatShareManager isScreenshotShare
let shot = try ImageProvider.captureScreenshot()
guard let imageData = shot.jpegData(compressionQuality: 0.6) else { return }
try await WeChatManager.shared.shareImage(
.init(
imageData: imageData,
thumb: thumb,
messageExt: content.desc,
mediaTagName: "\(Self.appTagPrefix)游戏截图分享"
),
scene: wxScene
)
} else {
// msext else type=="1"
try await WeChatManager.shared.shareLink(
.init(
url: content.webpageUrl,
title: content.title,
desc: content.desc,
thumb: thumb,
mediaTagName: "\(Self.appTagPrefix)游戏分享"
),
scene: wxScene
)
}
} catch {
// SDK H5 sharesuccess
}
} }
} }
// MARK: - NewRootVC.m // MARK: - NewRootVC.m
/// H5 friendsShare msext NewRootVC.m
/// :471-534 3 SharePanel
public func shareLobby(_ content: ShareContent, scene: ShareScene) { public func shareLobby(_ content: ShareContent, scene: ShareScene) {
guard WXApi.isWXAppInstalled() else { return } guard isInstalled else { return }
let wxScene: WeChatManager.ShareScene = (scene == .timeline) ? .timeline : .session switch content.type {
let appIcon = Self.appIconImage() case "1":
shareLink(content)
case "2":
shareScreenshot()
default:
shareRemoteImage(content)
}
}
// MARK: -
/// UIPasteboard.image ""
private func shareScreenshot() {
do {
let shot = try ImageProvider.captureScreenshot()
UIPasteboard.general.image = shot
Self.log.debug("WechatShare 截图已复制到剪贴板")
} catch {
Self.log.error("WechatShare 截图失败 \(error.localizedDescription, privacy: .public)")
return
}
let message = """
✅ 图片已复制到剪贴板
🎯 分享到好友、群聊或朋友圈步骤:
1️⃣ 打开微信,选择好友、群聊或朋友圈
2️⃣ 在输入框长按粘贴图片
3️⃣ 点击发送即可分享
"""
showGuidanceAlert(title: "微信分享准备完成", message: message)
}
// MARK: -
/// title + desc + url UIPasteboard.string
/// "" DouyinShare
private func shareLink(_ content: ShareContent) {
var parts: [String] = []
if !content.title.isEmpty { parts.append(content.title) }
if !content.desc.isEmpty { parts.append(content.desc) }
if !content.webpageUrl.isEmpty { parts.append(content.webpageUrl) }
let shareText = parts.isEmpty
? "来自进贤聚友棋牌的精彩内容分享"
: parts.joined(separator: "\n")
UIPasteboard.general.string = shareText
Self.log.debug("WechatShare 链接已复制到剪贴板")
let message = """
✅ 内容已复制到剪贴板
🎯 分享到好友、群聊或朋友圈步骤:
1️⃣ 打开微信,选择好友、群聊或朋友圈
2️⃣ 在输入框长按粘贴内容
3️⃣ 点击发送即可分享
"""
showGuidanceAlert(title: "微信分享准备完成", message: message)
}
// MARK: - shareLobby ""
/// UIPasteboard.image msext NewRootVC.m:526-534
/// "" SDK NSData
/// UIImage
private func shareRemoteImage(_ content: ShareContent) {
guard let url = URL(string: content.webpageUrl) else {
Self.log.error("WechatShare 远端图 URL 解析失败")
return
}
Task { @MainActor in Task { @MainActor in
do { do {
switch content.type { let (data, _) = try await URLSession.shared.data(from: url)
case "1": guard let image = UIImage(data: data) else {
// msext sharelogo.png fallback app icon Self.log.error("WechatShare 远端图解码失败")
try await WeChatManager.shared.shareLink( return
.init(
url: content.webpageUrl,
title: content.title,
desc: content.desc,
thumb: appIcon,
mediaTagName: "\(Self.appTagPrefix)游戏下载链接"
),
scene: wxScene
)
case "2":
// thumb = 40% msext imageByScalingAndCroppingForSize:0.4x
let shot = try ImageProvider.captureScreenshot()
guard let imageData = shot.jpegData(compressionQuality: 0.6) else { return }
try await WeChatManager.shared.shareImage(
.init(
imageData: imageData,
thumb: ImageProvider.scaledThumb(shot, scale: 0.4),
messageExt: content.desc,
mediaTagName: "\(Self.appTagPrefix)游戏截图分享"
),
scene: wxScene
)
default:
// type == "" / "3" / NSData +
// thumb = 40% messageExt/mediaTagName
// msext NewRootVC.m:530 H5 desc
guard let url = URL(string: content.webpageUrl) else { return }
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else { return }
try await WeChatManager.shared.shareImage(
.init(
imageData: data,
thumb: ImageProvider.scaledThumb(image, scale: 0.4),
messageExt: "\(Self.appTagPrefix)公众号二维码分享",
mediaTagName: "\(Self.appTagPrefix)公众号二维码分享"
),
scene: wxScene
)
} }
UIPasteboard.general.image = image
Self.log.debug("WechatShare 远端图已复制到剪贴板")
} catch { } catch {
// sharesuccess Self.log.error("WechatShare 远端图下载失败 \(error.localizedDescription, privacy: .public)")
return
} }
let message = """
✅ 图片已复制到剪贴板
🎯 分享到好友、群聊或朋友圈步骤:
1️⃣ 打开微信,选择好友、群聊或朋友圈
2️⃣ 在输入框长按粘贴图片
3️⃣ 点击发送即可分享
"""
showGuidanceAlert(title: "微信分享准备完成", message: message)
} }
} }
// MARK: - / // MARK: -
/// msext WechatShareManager "" daoqi /// " / " Alert QQShare / DouyinShare UX
/// SGDefineInfo.h gamehallname WXMediaMessage.mediaTagName private func showGuidanceAlert(title: String, message: String) {
private static let appTagPrefix = "进贤聚友棋牌" let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "立即打开微信", style: .default) { _ in
if let url = URL(string: Self.wechatScheme),
UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
})
alert.addAction(UIAlertAction(title: "稍后分享", style: .cancel))
/// app icon Info.plist `CFBundleIcons` `CFBundlePrimaryIcon` guard let topVC = Self.topViewController() else { return }
/// `CFBundleIconFiles` msext topVC.present(alert, animated: true)
/// WechatShareManager `getAppIconImage` 1x1 }
private static func appIconImage() -> UIImage {
if let icons = Bundle.main.object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any], private static func topViewController() -> UIViewController? {
let primary = icons["CFBundlePrimaryIcon"] as? [String: Any], let scene = UIApplication.shared.connectedScenes
let files = primary["CFBundleIconFiles"] as? [String], .compactMap { $0 as? UIWindowScene }
let last = files.last, .first(where: { $0.activationState == .foregroundActive })
let icon = UIImage(named: last) { ?? UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first
return icon guard var top = scene?.windows.first(where: \.isKeyWindow)?.rootViewController
?? scene?.windows.first?.rootViewController else { return nil }
while let presented = top.presentedViewController { top = presented }
if let nav = top as? UINavigationController, let visible = nav.visibleViewController {
return visible
} }
if let icon = UIImage(named: "AppIcon") { return icon } return top
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 1, height: 1))
return renderer.image { _ in }
} }
} }