Commit 41843963 authored by lujie's avatar lujie
Browse files

Merge branch 'feat-demo' into 'main'

feat(app): init

See merge request !1
parents 8d522ac0 b8124cc0
{
"license": "ISC",
"devDependencies": {},
"name": "tuanjieLib",
"description": "example description",
"repository": {},
"version": "1.0.0",
"dependencies": {
},
"main": "exported.ets"
}
\ No newline at end of file
import window from '@ohos.window';
import { AbilityConstant, Want } from '@kit.AbilityKit';
import UIAbility from '@ohos.app.ability.UIAbility';
import { BusinessError } from '@kit.BasicServicesKit';
import '../workers/Init';
import { DisplayInfoManager } from '../utils/DisplayInfoManager'
import { GetFromGlobalThis, SetToGlobalThis } from '../common/GlobalThisUtil';
import { Tuanjie } from '../utils/TuanjieNative';
import { TuanjieLog } from '../common/TuanjieLog';
import { POST_MESSAGE } from '../workers/WorkerProxy';
import { WindowUtils } from '../utils/WindowUtils'
import { VideoPlayerProxy } from '../utils/VideoPlayerProxy'
import { fastSdk, InitResult, LogUtil } from 'fastsdk';
import { GoodsInfo } from 'fastsdk/src/main/ets/model/GoodsInfo';
import { processMgr } from 'fastsdk/src/main/ets/utils/PageManager';
import { LoginResult } from 'fastsdk/src/main/ets/model/LoginResult';
export class TuanjiePlayerAbilityBase extends UIAbility {
pageUri: string = "pages/Index";
windowStage: window.WindowStage | undefined = undefined;
uiContext?:UIContext;
setPageUri(uri: string): void {
this.pageUri = uri;
}
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
TuanjieLog.info('%{public}s', 'TuanjiePlayerAbility onCreate, bundleCodeDir:' + this.context.bundleCodeDir);
if (this.context.resourceManager.getNumber(GetFromGlobalThis("showStaticSplash"))) {
globalThis.showStaticSplashScreen = true;
}
//fastsdk相关方法
fastSdk.onCreate(want,launchParam,this.context);
//SDK初始化回调注册
this.register();
Tuanjie.nativeOnCreate();
if (typeof want.parameters !== "undefined") {
globalThis.CommandLineArguments = want.parameters["unity"]?.toString();
}
POST_MESSAGE({
type: 'SetGlobalThisContext', data: this.context
});
globalThis.AbilityContext = this.context;
DisplayInfoManager.getInstance().initialize();
AppStorage.setOrCreate("backButtonLeavesApp", false);
}
onDestroy(): void {
TuanjieLog.info('%{public}s', 'TuanjiePlayerAbility onDestroy');
Tuanjie.nativeOnDestroy();
//fastsdk相关方法
fastSdk.onDestory();
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
TuanjieLog.info('%{public}s', 'TuanjiePlayerAbility onWindowStageCreate');
this.windowStage = this.windowStage;
// set full screen at first
let windowClass = windowStage.getMainWindowSync();
try {
SetToGlobalThis(WindowUtils.getInstance().MainWindowKey, windowClass);
WindowUtils.getInstance().setWindowSizeChangeCallback();
WindowUtils.getInstance().setWindowAvoidAreaChangeCallBack();
windowClass.setWindowLayoutFullScreen(true, (err, data) => {
if (err.code) {
console.error('Failed to enable the full-screen mode. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in enabling the full-screen mode. Data: ' + JSON.stringify(data));
});
} catch (err) {
console.error('Failed to obtain the main window. Cause: ' + JSON.stringify(err));
}
//请确保windowStage.loadContent中pageUri为主界面时调用fastSdk相关接口
// this.pageUri = 'pages/TuanjiePlayerAbilityIndex';
windowStage.loadContent(this.pageUri, (err, data) => {
if (err.code) {
TuanjieLog.error('Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
TuanjieLog.info('Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
this.uiContext = windowStage.getMainWindowSync().getUIContext();
//fastsdk相关方法
fastSdk.loadContentSuccess(windowStage);
//SDK初始化
fastSdk.initSDK();
});
// set callback
windowStage.on('windowStageEvent', (data) => {
let stageEventType: window.WindowStageEventType = data;
switch (stageEventType) {
case window.WindowStageEventType.ACTIVE:
TuanjieLog.info('windowStage active.');
Tuanjie.nativeOnWindowStageActive();
break;
case window.WindowStageEventType.INACTIVE:
TuanjieLog.info('windowStage inactive.');
Tuanjie.nativeOnWindowStageInActive();
break;
case window.WindowStageEventType.RESUMED:
TuanjieLog.info('windowStage resumed.');
this.onResume();
break;
case window.WindowStageEventType.PAUSED:
TuanjieLog.info('windowStage paused.');
this.onPause();
break;
default:
break;
}
})
}
onWindowStageWillDestroy(windowStage: window.WindowStage): void {
}
onWindowStageDestroy(): void {
TuanjieLog.info('%{public}s', 'TuanjiePlayerAbility onWindowStageDestroy');
try {
if (this.windowStage) {
this.windowStage.off('windowStageEvent');
}
} catch (err) {
let code = (err as BusinessError).code;
let errMsg = (err as BusinessError).message;
TuanjieLog.error(`Failed to disable listener for windowStageEvent. code=${code} errmsg=${errMsg}`);
}
}
onForeground(): void {
TuanjieLog.info('%{public}s', 'TuanjiePlayerAbility onForeground');
this.onResume();
}
onBackground(): void {
TuanjieLog.info('%{public}s', 'TuanjiePlayerAbility onBackground');
this.onPause();
}
private onResume(): void {
if (VideoPlayerProxy.getInstance().IsPlaying()) {
VideoPlayerProxy.getInstance().OnResume();
}
else {
Tuanjie.nativeOnResume();
}
POST_MESSAGE({
type: 'InternetSubscribe', data: null
});
}
private onPause(): void {
if (VideoPlayerProxy.getInstance().IsPlaying()) {
VideoPlayerProxy.getInstance().OnPause();
}
else {
Tuanjie.nativeOnPause();
}
}
private register():void {
fastSdk.hlSystemListener = {
onInitSuccess: (result: InitResult) => {
LogUtil.info('初始化成功:' + JSON.stringify(result));
},
onInitFailed: (reason: string) => {
LogUtil.error('初始化失败:' + reason);
},
onCustomExit: () => {
LogUtil.info('自定义退出流程触发');
this.uiContext?.showAlertDialog({
title:'自定义退出',
message:'确定退出游戏?',
autoCancel:true,
alignment:DialogAlignment.Center,
buttons:[{
value:"再玩一会",
action:()=>{
LogUtil.info("点击了再玩一会");
}
},{
enabled:true,
defaultFocus:true,
style:DialogButtonStyle.HIGHLIGHT,
value:'确定退出',
action:()=>{
//自定义退出弹窗
LogUtil.info('自定义退出成功');
fastSdk.hlSystemListener?.onExitSuccess("");
}
}]
})
},
onExitSuccess: (result: string) => {
LogUtil.info('退出成功:' + result);
processMgr.exit(0)
}
};
fastSdk.hlAccountListener = {
onRefreshUser: (result: LoginResult): void => {
//暂不使用
},
onLoginSuccess: (result: LoginResult): void => {
LogUtil.info('登录成功:' + JSON.stringify(result));
},
onLoginFailed: (reason: string): void => {
LogUtil.info('登录失败:' + reason);
},
onLogout: (): void => {
LogUtil.info('已登出');
}
};
fastSdk.hlPaymentListener = {
onPaySuccess: (result: string): void => {
LogUtil.info(`支付成功:${result}`);
},
onPayFailed: (reason: string): void => {
LogUtil.info(`支付失败:${reason}`);
},
onQuerySuccess: (products: GoodsInfo[]): void => {
//暂不使用
}
}
}
}
export class Constants {
static readonly APP_KEY_TUANJIE_MAIN_WORKER = "app_key_tuanjie_main_worker";
static readonly APP_KEY_CALLBACK_REGISTER = "app_key_callback_register";
}
// do not set PropName of AppStorage using static class member,
// otherwise compiler error "The decorator StorageProp should have a single key" will be generated
export const APP_KEY_SAFEAREA_RECT= "app_key_safearea_rect";
export const APP_KEY_ORIENTATION_CHANGE = "app_key_orientation_change";
\ No newline at end of file
export function SetToGlobalThis(key: string, obj: unknown): void {
globalThis[key] = obj;
}
export function GetFromGlobalThis(key: string) {
return globalThis[key];
}
export function InitGlobalThisContext(data, PlayerPrefs, TuanjieInternet, TuanjiePermissions) {
globalThis.context = data;
globalThis.context.PlayerPrefs = PlayerPrefs;
globalThis.context.TuanjieInternet = TuanjieInternet;
globalThis.context.TuanjiePermissions = TuanjiePermissions;
}
export function SetToGlobalThisContext(key, value) {
globalThis.context[key] = value;
}
export function GetFromGlobalThisContext(key) {
return globalThis.context[key];
}
import fs from '@ohos.file.fs';
import { REGISTER_BUILTIN_MODULE, MSG_RECEIVER } from '../workers/MessageProcessor';
import { GetFromGlobalThis } from './GlobalThisUtil';
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
export class PlayerPrefs {
public static directoryInt : Map<string, number> = new Map<string, number>();
public static directoryFloat : Map<string, number>= new Map<string, number>();
public static directoryString : Map<string, string>= new Map<string, string>();
private static intPreference: preferences.Preferences | null = null;
private static floatPreference: preferences.Preferences | null = null;
private static stringPreference: preferences.Preferences | null = null;
public static Init(filePathPrefix : string) {
if (PlayerPrefs.intPreference)
return;
let context: common.Context = GetFromGlobalThis('context');
PlayerPrefs.intPreference = preferences.getPreferencesSync(context, { name: "tj_intPreference" });
PlayerPrefs.floatPreference = preferences.getPreferencesSync(context, { name: "tj_floatPreference" });
PlayerPrefs.stringPreference = preferences.getPreferencesSync(context, { name: "tj_stringPreference" });
// compatible with older versions, if PlayerPreference.txt exists, read from it.
let filePath = filePathPrefix + "/PlayerPreference.txt";
if (fs.accessSync(filePath)) {
PlayerPrefs.LoadFormFile(filePath);
}
else {
let intKV = PlayerPrefs.intPreference?.getAllSync() as Map<string, number>;
let keys = Object.keys(intKV);
keys.forEach((key) =>
{
PlayerPrefs.directoryInt[key] = intKV[key];
});
let floatKV = PlayerPrefs.floatPreference?.getAllSync() as Map<string, number>;
keys = Object.keys(floatKV);
keys.forEach((key) =>
{
PlayerPrefs.directoryFloat[key] = floatKV[key];
});
let stringKV = PlayerPrefs.stringPreference?.getAllSync() as Map<string, string>;
keys = Object.keys(stringKV);
keys.forEach((key) =>
{
PlayerPrefs.directoryString[key] = stringKV[key];
});
}
}
private static LoadFormFile(filePath: string) {
let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
fs.closeSync(file);
let jsonInt = "{}";
let jsonFloat = "{}";
let jsonString = "{}";
let str = fs.readTextSync(filePath);
if (str != "") {
let arr = str.split('\n');
jsonInt = arr[0];
jsonFloat = arr[1];
jsonString = arr[2];
}
let directoryInt:Map<string, string | number | boolean> = JSON.parse(jsonInt);
let directoryFloat:Map<string, string | number | boolean> = JSON.parse(jsonFloat);
let directoryString:Map<string, string | number | boolean> = JSON.parse(jsonString);
let keys = Object.keys(directoryInt);
keys.forEach((element) =>
{
PlayerPrefs.directoryInt[element] = directoryInt[element];
PlayerPrefs.intPreference?.put(element, directoryInt[element]);
});
keys = Object.keys(directoryFloat);
keys.forEach((element) =>
{
PlayerPrefs.directoryFloat[element] = directoryFloat[element];
PlayerPrefs.floatPreference?.put(element, directoryFloat[element]);
});
keys = Object.keys(directoryString);
keys.forEach((element) =>
{
PlayerPrefs.directoryString[element] = directoryString[element];
PlayerPrefs.stringPreference?.put(element, directoryString[element]);
});
PlayerPrefs.intPreference?.flush();
PlayerPrefs.floatPreference?.flush();
PlayerPrefs.stringPreference?.flush();
fs.unlinkSync(filePath);
}
public static SetInt(name:string, value:number) {
PlayerPrefs.directoryInt[name] = value;
PlayerPrefs.intPreference?.put(name, value);
PlayerPrefs.intPreference?.flush();
}
public static SetFloat(name:string, value:number) {
PlayerPrefs.directoryFloat[name] = value;
PlayerPrefs.floatPreference?.put(name, value);
PlayerPrefs.floatPreference?.flush();
}
public static SetString(name:string, value:string) {
PlayerPrefs.directoryString[name] = value;
PlayerPrefs.stringPreference?.put(name, value);
PlayerPrefs.stringPreference?.flush();
}
public static GetInt(name:string, def:number) : number {
if (PlayerPrefs.directoryInt[name] == null)
return def;
return PlayerPrefs.directoryInt[name];
}
public static GetFloat(name:string, def:number) : number{
if (PlayerPrefs.directoryFloat[name] == null)
return def;
return PlayerPrefs.directoryFloat[name];
}
public static GetString(name:string, def : string) :string{
if (PlayerPrefs.directoryString[name] == null)
return def;
return PlayerPrefs.directoryString[name];
}
public static HasKey(name:string) : boolean {
if (PlayerPrefs.directoryString[name] != null)
return true;
if (PlayerPrefs.directoryFloat[name] != null)
return true;
if (PlayerPrefs.directoryInt[name] != null)
return true;
return false;
}
public static DeleteKey(name:string) {
if (PlayerPrefs.directoryString[name] != null) {
PlayerPrefs.directoryString[name] = null;
PlayerPrefs.stringPreference?.delete(name);
PlayerPrefs.stringPreference?.flush();
}
if (PlayerPrefs.directoryFloat[name] != null){
PlayerPrefs.directoryFloat[name] = null;
PlayerPrefs.floatPreference?.delete(name);
PlayerPrefs.floatPreference?.flush();
}
if (PlayerPrefs.directoryInt[name] != null){
PlayerPrefs.directoryInt[name] = null;
PlayerPrefs.intPreference?.delete(name);
PlayerPrefs.intPreference?.flush();
}
}
public static DeleteAll() {
let intkeys = Object.keys(PlayerPrefs.directoryInt);
intkeys.forEach((element) => PlayerPrefs.directoryInt[element] = null);
let floatkeys = Object.keys(PlayerPrefs.directoryFloat);
floatkeys.forEach((element) => PlayerPrefs.directoryFloat[element] = null);
let stringkeys = Object.keys(PlayerPrefs.directoryString);
stringkeys.forEach((element) => PlayerPrefs.directoryString[element] = null);
PlayerPrefs.intPreference?.clearSync();
PlayerPrefs.intPreference?.flushSync();
PlayerPrefs.floatPreference?.clearSync();
PlayerPrefs.floatPreference?.flushSync();
PlayerPrefs.stringPreference?.clearSync();
PlayerPrefs.stringPreference?.flushSync();
}
public static Save() {
PlayerPrefs.intPreference?.flushSync();
PlayerPrefs.floatPreference?.flushSync();
PlayerPrefs.stringPreference?.flushSync();
}
public static LocalSave() {
PlayerPrefs.Save();
}
}
!!REGISTER_BUILTIN_MODULE && REGISTER_BUILTIN_MODULE(MSG_RECEIVER.HOST_UI, PlayerPrefs);
\ No newline at end of file
import hilog from '@ohos.hilog';
export class TuanjieLog {
private static readonly DOMAIN = 0x0001;
private static readonly TUANJIE_TAG = "Tuanjie";
public static debug(format: string, ...args: ESObject[]): void {
hilog.debug(TuanjieLog.DOMAIN, TuanjieLog.TUANJIE_TAG, format, args);
}
public static info(format: string, ...args: ESObject[]): void {
hilog.info(TuanjieLog.DOMAIN, TuanjieLog.TUANJIE_TAG, format, args);
}
public static warn(format: string, ...args: ESObject[]): void {
hilog.warn(TuanjieLog.DOMAIN, TuanjieLog.TUANJIE_TAG, format, args);
}
public static error(format: string, ...args: ESObject[]): void {
hilog.error(TuanjieLog.DOMAIN, TuanjieLog.TUANJIE_TAG, format, args);
}
public static fatal(format: string, ...args: ESObject[]): void {
hilog.fatal(TuanjieLog.DOMAIN, TuanjieLog.TUANJIE_TAG, format, args);
}
}
// Auto-generated by build-program, please don't modify it manually unless you known what you're doing
import '../common/PlayerPrefs'
import '../utils/GalleryHandler'
import '../utils/SoftInputUtils'
import '../utils/SystemSettings'
import '../utils/TuanjieAAID'
import '../utils/TuanjieInternet'
import '../utils/TuanjieLocation'
import '../utils/TuanjieOpenURL'
import '../utils/TuanjiePermissions'
import '../utils/TuanjieVibrate'
import '../utils/VideoPlayerProxy'
import '../utils/WebviewUtils'
import '../utils/WindowUtils'
// Auto-generated by build-program, please don't modify it manually unless you known what you're doing
import { RegisterGalleryManager } from '../utils/GalleryManager';
import { RegisterWebviewControl } from '../utils/WebviewControl'
import { TuanjieLog } from '../common/TuanjieLog';
function register(tuanjieJSClasses:Record<string, Object>, functionName :Function) {
let exportObj:Record<string, Object> = functionName();
for (let key of Object.keys(exportObj)) {
if(tuanjieJSClasses[key] !== undefined){
TuanjieLog.error('Duplicate keys detected when exporting TypeScript module to C#: ' + key + '. This might cause overwrite exists module.');
}
tuanjieJSClasses[key] = exportObj[key];
}
}
export function registerJSScriptToCSharp() {
let tuanjieJSClasses:Record<string, Object> = {};
register(tuanjieJSClasses, RegisterGalleryManager);
register(tuanjieJSClasses, RegisterWebviewControl);
return tuanjieJSClasses;
}
\ No newline at end of file
import { APP_KEY_SAFEAREA_RECT } from '../common/Constants';
import { TuanjiePlayerView } from './components/TuanjiePlayerView';
import { window } from '@kit.ArkUI';
@Entry
@Component
struct TuanjiePlayer {
@State displayIndex0: number = 0;
@State displayIndex1: number = 0;
@State displayIndex2: number = 2;
@State btnText: string = "TurnOffDisplay2";
private multiView: boolean = false;
@StorageProp(APP_KEY_SAFEAREA_RECT) safeAreaRect: window.Rect = { top:0,left:0,width:0,height:0};
build() {
Column() {
Row() {
if (!this.multiView) {
TuanjiePlayerView({displayIndex: this.displayIndex0});
} else {
Button(this.btnText)
.onClick(() => {
// switch on/off display2
if (this.displayIndex2 == -1) {
this.btnText = "TurnOffDisplay2";
this.displayIndex2 = 2;
} else {
this.btnText = "TurnOnDisplay2";
this.displayIndex2 = -1;
}
// // navigate to page 2
// this.getUIContext().getRouter()
// .pushUrl({
// url: "pages/Index2",
// params: {
// data: "message",
// }
// })
// .then(() => {
// console.info("Page transition succeed");
// })
// .catch((error: BusinessError) => {
// console.error(`pushUrl failed, code is ${error.code}, message is ${error.message}`);
// })
})
TuanjiePlayerView({ displayIndex: this.displayIndex1 }).width('50%');
Column({ space: "3" }) {
TuanjiePlayerView({ displayIndex: this.displayIndex2 }).width('80%');//.height('580px').width('580px');
}.width('40%');
}
}
.margin({ left: `${this.safeAreaRect.left}px`, top: `${this.safeAreaRect.top}px` })
.width(`${this.safeAreaRect.width}px`)
.height(`${this.safeAreaRect.height}px`)
.alignItems(VerticalAlign.Center)
}
.alignItems(HorizontalAlign.Start)
.backgroundColor(Color.Black)
.height('100%')
.width('100%')
}
onBackPress() {
return !AppStorage.get("backButtonLeavesApp") as boolean;
}
}
import promptAction from '@ohos.promptAction';
import { SetToGlobalThis } from '../../common/GlobalThisUtil';
import { TuanjieLog } from '../../common/TuanjieLog';
import { DebuggerDialogInfo } from '../../utils/DebuggerDialogInfo'
import { Tuanjie } from '../../utils/TuanjieNative';
@Component
export struct DebuggerDialog {
@Link debuggerDialogInfo: DebuggerDialogInfo;
build() {}
aboutToAppear()
{
promptAction.showDialog({
title: this.debuggerDialogInfo.Title,
message: this.debuggerDialogInfo.Message,
buttons: [
{
text: this.debuggerDialogInfo.buttonText,
color: "#000000"
}
]
}).then( data => {
Tuanjie.nativeDebuggerDialogClosed();
this.debuggerDialogInfo.Showing = false;
});
}
};
\ No newline at end of file
import { GetFromGlobalThis } from '../../common/GlobalThisUtil'
@CustomDialog
export struct SoftInputDialog {
private showMessage: string = ''
private inputMessage: string = ''
onTextChange: (msg: string) => void = () => {
}
onTextSelectionChange: (start: number, end: number) => void = () => {
}
accept: (msg: string) => void = () => {
}
controller?: CustomDialogController
cancel: () => void = () => {
}
confirm: () => void = () => {
}
build() {
Column() {
Row() {
TextInput({ text: GetFromGlobalThis('inputInitialText') })
.backgroundColor('#ffffff')
.layoutWeight(1)
.onChange((value) => {
if (this.onTextChange) {
this.onTextChange(value);
}
this.inputMessage = value;
})
.onTextSelectionChange((start, end) => {
if (this.onTextSelectionChange) {
this.onTextSelectionChange(start, end);
}
})
.onSubmit((value) => {
if (this.accept) {
this.accept(this.inputMessage);
}
this.controller!.close();
})
.defaultFocus(true)
Blank(8).width(16)
Button('完成').onClick(() => {
if (this.accept) {
this.accept(this.inputMessage);
}
this.controller!.close();
})
}.padding({ left: 8, right: 8, top: 8, bottom: 8 })
.backgroundColor(Color.Gray)
}
.width('100%')
.justifyContent(FlexAlign.End)
}
}
import resourceManager from '@ohos.resourceManager';
import { GetFromGlobalThis } from '../../common/GlobalThisUtil';
@Component
export struct StaticSplashScreen {
@State showStaticSplashScreen: boolean = GetFromGlobalThis('showStaticSplashScreen');
@State resConf: Resource = GetFromGlobalThis('staticSplashScreenFit');
@State imageRes: Resource = GetFromGlobalThis('appSplash');
@State imageFit: ImageFit = 0;
async aboutToAppear() {
let resourceManager = getContext(this).resourceManager;
let resource: resourceManager.Resource = {
bundleName: this.resConf.bundleName,
moduleName: this.resConf.moduleName,
id: this.resConf.id
}
this.imageFit = await resourceManager.getNumber(resource);
setTimeout(() => {
this.showStaticSplashScreen = false
}, 2600)
}
build() {
Stack() {
if (this.showStaticSplashScreen) {
Image(this.imageRes).objectFit(this.imageFit)
}
}
}
}
\ No newline at end of file
import { uiObserver, UIObserver } from '@kit.ArkUI';
import web_webview from '@ohos.web.webview'
import { APP_KEY_ORIENTATION_CHANGE } from '../../common/Constants';
import { DebuggerDialog } from './DebuggerDialog';
import { SoftInputDialog } from './SoftInputDialog'
import { TuanjieView } from './TuanjieView';
import { StaticSplashScreen } from './StaticSplashScreen';
import { WebviewInfo, TuanjieWebview } from "./TuanjieWebview"
import { VideoPlayer } from './VideoPlayer'
import { SetToGlobalThis } from '../../common/GlobalThisUtil';
import { TuanjieLog } from '../../common/TuanjieLog';
import { WindowUtils } from '../../utils/WindowUtils';
import { POST_MESSAGE } from '../../workers/WorkerProxy';
import { DebuggerDialogInfo } from '../../utils/DebuggerDialogInfo';
import { Tuanjie } from '../../utils/TuanjieNative';
@Component
export struct TuanjiePlayerView {
@StorageLink('webviewInfos') webviewInfos: WebviewInfo[] = [];
@StorageLink('controllers') controllers: web_webview.WebviewController[] = [];
@StorageProp(APP_KEY_ORIENTATION_CHANGE) @Watch('updateOrientation') orientation: number = 0;
@State debuggerDialogInfo : DebuggerDialogInfo = new DebuggerDialogInfo("","","",false);
@State vAlign: FlexAlign = FlexAlign.End;
@State hAlign: FlexAlign = FlexAlign.End;
@Link displayIndex: number;
listener: (info: uiObserver.RouterPageInfo) => void = (info: uiObserver.RouterPageInfo) => {
let routerInfo: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo();
if (info.pageId == routerInfo?.pageId) {
if (info.state == uiObserver.RouterPageState.ON_PAGE_SHOW) {
this.onShow();
} else if (info.state == uiObserver.RouterPageState.ON_PAGE_HIDE) {
this.onHide();
}
}
}
updateOrientation() {
// portrait upside down
this.vAlign = this.orientation == 2 ? FlexAlign.Start : FlexAlign.End;
// landscape right
this.hAlign = this.orientation == 1 ? FlexAlign.Start : FlexAlign.End;
TuanjieLog.info('%{public}s', "updateOrientation");
}
dialogController = new CustomDialogController({
builder: SoftInputDialog({
// showMessage: this.showMessage,
onTextChange: (msg: string) => {
TuanjieLog.debug("CustomDialogController onTextChange " + msg);
POST_MESSAGE({ type: 'SoftInput_onTextChange', data: msg });
},
onTextSelectionChange: (start: number, end: number) => {
TuanjieLog.debug("CustomDialogController onTextSelectionChange start: " + start.toString() + ", end: " + end.toString());
POST_MESSAGE({ type: 'SoftInput_onTextSelectionChange', start: start, length: end - start });
},
accept: (msg: string) => {
TuanjieLog.debug("CustomDialogController accept" + msg);
POST_MESSAGE({ type: 'SoftInput_accept', data: msg });
},
}),
cancel: () => {
TuanjieLog.debug("CustomDialogController cancel");
POST_MESSAGE({ type: 'SoftInput_accept', data: null });
},
autoCancel: true,
alignment: DialogAlignment.Bottom,
customStyle: true,
})
onShow() {
TuanjieLog.info('%{public}s', 'onShow');
this.updateOrientation();
WindowUtils.getInstance().setXComponentSizeWithSafeArea(0, 0, Tuanjie.nativeGetIsRenderOutsizeSafeArea());
SetToGlobalThis('dialogController', this.dialogController);
SetToGlobalThis(DebuggerDialogInfo.DebuggerDialogInfoKey, this.debuggerDialogInfo);
let ctx: UIContext = this.getUIContext();
SetToGlobalThis('UIContext',ctx);
}
onHide() {
TuanjieLog.info('%{public}s', 'onHide');
}
onPageShow() {
TuanjieLog.info('%{public}s', 'onPageShow -111');
this.onShow();
}
onPageHide() {
TuanjieLog.info('%{public}s', 'onPageHide');
this.onHide();
}
aboutToAppear(): void {
let uiOb: UIObserver = this.getUIContext().getUIObserver();
uiOb.on("routerPageUpdate", this.listener);
}
aboutToDisappear(): void {
let uiOb: UIObserver = this.getUIContext().getUIObserver();
uiOb.off("routerPageUpdate", this.listener);
}
build() {
Row() {
Column() {
Stack() {
TuanjieView({displayIndex: this.displayIndex});
TuanjieWebview({viewInfos: this.webviewInfos, controllers: this.controllers});
StaticSplashScreen()
VideoPlayer()
if (this.debuggerDialogInfo.Showing) {
DebuggerDialog({debuggerDialogInfo: this.debuggerDialogInfo})
}
}
}
.height('100%')
.backgroundColor('#ff000000')
.justifyContent(this.vAlign)
}
.width('100%')
.backgroundColor('#ff000000')
.justifyContent(this.hAlign)
}
}
import { TuanjieLog } from '../../common/TuanjieLog';
import Tuanjie from 'libtuanjie.so'
class TuanjieViewInfo {
private surfaceId: number;
private displayIndex: number;
private xComponentId: number;
private windowId: number;
constructor(surfaceId: number, index: number) {
this.surfaceId = surfaceId;
this.displayIndex = index;
this.xComponentId = 0;
this.windowId = 0;
}
DisplayIndex(): number {
return this.displayIndex;
}
UpdateDisplayIndex(displayIndex: number) {
this.displayIndex = displayIndex;
Tuanjie.nativeUpdateDisplay(displayIndex, this.xComponentId, this.windowId);
}
XComponentId(): number {
return this.xComponentId;
}
WindowId(): number {
return this.windowId;
}
UpdateWindowInfo(componentId: number, windowId: number) {
this.xComponentId = componentId;
this.windowId = windowId;
}
toString(): string {
return `TuanjieViewInfo:${this.surfaceId}-${this.displayIndex}-${this.xComponentId}-${this.windowId}`;
}
}
class TuanjieViewController extends XComponentController {
static getSurfaceDisplayIndexSet: boolean = false;
static surface2DisplayIndex: Record<number, TuanjieViewInfo> = {};
static getDisplayIndex(xcomponentId: number, windowId: number, surfaceId: number) {
console.log(`getDisplayIndex| surfaceId=${surfaceId}`);
let info: TuanjieViewInfo = TuanjieViewController.surface2DisplayIndex[surfaceId];
if (info != undefined) {
info.UpdateWindowInfo(xcomponentId, windowId);
return info.DisplayIndex();
}
return -1;
}
onDidBuild(displayIndex: number) {
let strSurfaceId: string = this.getXComponentSurfaceId();
let surfaceId : number = parseInt(strSurfaceId, 10);
TuanjieLog.info('%{public}s', `onDidBuild| surfaceId=${surfaceId} displayIndex=${displayIndex}}`);
TuanjieViewController.surface2DisplayIndex[surfaceId] = new TuanjieViewInfo(surfaceId, displayIndex);
if (!TuanjieViewController.getSurfaceDisplayIndexSet) {
Tuanjie.nativeSetGetSurfaceDisplayIndexFunc(TuanjieViewController.getDisplayIndex);
TuanjieViewController.getSurfaceDisplayIndexSet = true;
}
}
updateDisplayIndex(displayIndex: number) {
let strSurfaceId: string = this.getXComponentSurfaceId();
let surfaceId : number = parseInt(strSurfaceId, 10);
let info: TuanjieViewInfo = TuanjieViewController.surface2DisplayIndex[surfaceId];
if (info != undefined) {
TuanjieLog.info('%{public}s', `updateDisplayIndex| ${info} new displayindex=${displayIndex}`);
info.UpdateDisplayIndex(displayIndex);
} else {
TuanjieLog.warn('%{public}s', `updateDisplayIndex| No window info with surfaceId=${strSurfaceId}`);
}
}
}
@Component
export struct TuanjieView {
@Prop index: number = 0;
@Link @Watch("onDisplayIndexChanged") displayIndex: number;
private controller: XComponentController = new TuanjieViewController();
build() {
Stack() {
XComponent({ id: 'TuanjiePlayer', type: 'surface', libraryname: 'tuanjie', controller: this.controller })
.onLoad(() => {
TuanjieLog.info('%{public}s', `XComponent ${this.displayIndex} onLoad`);
})
.onDestroy(() => {
TuanjieLog.info('%{public}s', `XComponent ${this.displayIndex} onDestroy`);
})
.onAppear(() => {
TuanjieLog.info('%{public}s', `XComponent ${this.displayIndex} onAppear`);
})
.onDisAppear(() => {
TuanjieLog.info('%{public}s', `XComponent ${this.displayIndex} onDisAppear`);
})
.defaultFocus(true)
.focusable(true)
.width('100%')
.height('100%');
}
}
onDidBuild(): void {
TuanjieLog.info('%{public}s', `XComponent onDidBuild surfaceId=${this.controller.getXComponentSurfaceId()} displayIndex=${this.displayIndex}}`);
if (this.displayIndex != -1) {
let ctrl: TuanjieViewController = this.controller as TuanjieViewController;
ctrl?.onDidBuild(this.displayIndex);
}
}
onDisplayIndexChanged() {
let ctrl: TuanjieViewController = this.controller as TuanjieViewController;
ctrl?.updateDisplayIndex(this.displayIndex);
}
}
import web_webview from '@ohos.web.webview'
import { TuanjieLog } from '../../common/TuanjieLog';
import { POST_MESSAGE } from '../../workers/WorkerProxy';
@Component
export struct TuanjieWebview {
@Link viewInfos: Array<WebviewInfo>;
@Link controllers: Array<web_webview.WebviewController>;
build() {
if (this.viewInfos && this.viewInfos.length > 0) {
ForEach(this.viewInfos, (info: WebviewInfo) => {
if (info.valid) {
Web({
src: info.url,
controller: this.controllers[info.index]
})
.position({ x: px2vp(info.x), y: px2vp(info.y) })
.width(px2vp(info.w))
.height(px2vp(info.h))
.border({ width: 1 })
.domStorageAccess(true)
.databaseAccess(true)
.imageAccess(true)
.javaScriptAccess(true)
.visibility(info.visible ? Visibility.Visible : Visibility.Hidden)
.onControllerAttached(() => {
POST_MESSAGE({
'type': 'WebviewStateCallback',
'index': info.index,
'state': WebviewState.Initialized,
})
})
.onPageEnd((event) => {
POST_MESSAGE({
'type': 'WebviewStateCallback',
'index': info.index,
'state': WebviewState.Loaded,
})
})
.onHttpErrorReceive((event) => {
if (event) {
const errorMsg: string = 'Response Code: ' + event.response.getResponseCode();
POST_MESSAGE({
'type': 'WebviewEventCallback',
'event': "HttpError",
'index': info.index,
'errorMsg': errorMsg
})
}
})
.onErrorReceive((event) => {
if (event) {
const errorMsg: string =
'Error Code: ' + event.error.getErrorCode() + ". Error Info: " + event.error.getErrorInfo();
POST_MESSAGE({
'type': 'WebviewEventCallback',
'event': "Error",
'index': info.index,
'errorMsg': errorMsg
})
}
})
}
})
}
}
}
export enum WebviewState {
Invalid = -1,
Initing = 0,
Initialized = 1,
Loading = 2,
Loaded = 3,
}
export class WebviewInfo {
public index = -1;
public valid = false;
// position
public x: number = 0;
public y: number = 0;
// size
public w: number = 0;
public h: number = 0;
// url
public url: string = '';
public visible: boolean = false;
public controller: web_webview.WebviewController = new web_webview.WebviewController()
constructor(index: number, x: number, y: number, w: number, h: number) {
this.index = index;
this.valid = true;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
public reset() {
this.valid = false;
this.x = 0;
this.y = 0;
this.w = 0;
this.h = 0;
this.url = '';
this.visible = false;
}
}
\ No newline at end of file
import { VideoInfo, VideoPlayerProxy, EVideoStatus } from '../../utils/VideoPlayerProxy'
@Component
export struct VideoPlayer {
@StorageLink('VideoInfo') @Watch('onVideoInfoUpdated') videoInfo: VideoInfo = new VideoInfo()
@StorageLink('VideoStatus') @Watch('onVideoStatusUpdated') videoStatus: EVideoStatus = EVideoStatus.None
private controller:VideoController = new VideoController()
private url: string = ''
private bgColor: number = 0
private controlShow: boolean = true
private scalingMode: ImageFit = ImageFit.Contain
onVideoInfoUpdated(propName: string): void {
this.url = this.videoInfo.url
this.bgColor = this.videoInfo.bgColor
this.controlShow = this.videoInfo.controlMode < 2
switch (this.videoInfo.scalingMode) {
case 0:
this.scalingMode = ImageFit.None
break
case 1:
this.scalingMode = ImageFit.Contain
break
case 2:
this.scalingMode = ImageFit.Cover
break
case 3:
this.scalingMode = ImageFit.Fill
break
}
}
onVideoStatusUpdated(propName: string): void {
if (!this.videoInfo.isPlaying) {
return;
}
switch (this.videoStatus) {
case EVideoStatus.Play:
this.controller.start();
break
case EVideoStatus.Pause:
this.controller.pause();
break
default :
}
}
start(): void {
VideoPlayerProxy.getInstance().OnVideoStart()
}
stop(): void {
this.videoInfo.isPlaying = false
this.videoStatus = EVideoStatus.Stop
VideoPlayerProxy.getInstance().OnVideoStop()
}
build() {
if (this.videoInfo.isPlaying) {
Stack() {
Video({
src: this.url,
controller: this.controller
})
.controls(this.controlShow)
.autoPlay(true)
.objectFit(this.scalingMode)
.loop(false)
.onStart(() => {
this.start()
})
.onFinish(() => {
this.stop()
})
.onError(() => {
this.stop()
})
.backgroundColor(this.bgColor)
.onClick((event: ClickEvent) => {
if (this.videoInfo.controlMode == 2) {
this.stop()
}
})
}
}
}
}
\ No newline at end of file
import { GetFromGlobalThis } from '../common/GlobalThisUtil';
export class DebuggerDialogInfo {
public Title: string = ""
public Message: string = ""
public buttonText: string = "OK"
public Showing: boolean;
constructor(Title: string, Message: string, buttonText: string, Showing: boolean) {
this.Title = Title
this.Message = Message
this.buttonText = buttonText
this.Showing = Showing
}
static readonly DebuggerDialogInfoKey = "debuggerDialogInfo";
static getDebuggerDialogInfo() : DebuggerDialogInfo {
return GetFromGlobalThis(DebuggerDialogInfo.DebuggerDialogInfoKey);
}
static setDebuggerDialogInfo(dialogTitle: string, dialogMessage: string, dialogButtonText: string , dialogDisplay: boolean) {
let debuggerDialogInfo:DebuggerDialogInfo = DebuggerDialogInfo.getDebuggerDialogInfo();
debuggerDialogInfo.Title = dialogTitle
debuggerDialogInfo.Message = dialogMessage
debuggerDialogInfo.buttonText = dialogButtonText
debuggerDialogInfo.Showing = dialogDisplay;
}
}
\ No newline at end of file
import { TuanjieLog } from '../common/TuanjieLog';
import display from '@ohos.display';
import { POST_MESSAGE } from '../workers/WorkerProxy'
import { APP_KEY_ORIENTATION_CHANGE } from '../common/Constants';
export class TuanjieDisplayInfo {
displayID: number = 0;
width: number = 0;
height: number = 0;
rotation: number = 0;
refreshRate: number = 0;
densityDPI: number = 0;
}
export class DisplayInfoManager {
// make rotation index consistent with android
static readonly openHarmonyToNativeRotationMap: Record<number, number> = {
0: 0,
1: 3,
2: 2,
3: 1
};
// todo ohos: don't support multi displayi now
defaultDisplay: TuanjieDisplayInfo;
private static instance = new DisplayInfoManager();
private constructor() {
TuanjieLog.debug('%{public}s', 'OhosSysInfo.constructor');
this.defaultDisplay = new TuanjieDisplayInfo();
}
public initialize(): void {
this.updateDisplayInfo();
this.enableDisplayChangeCallback();
}
private enableDisplayChangeCallback(): void {
let callback = (data: ESObject) => {
TuanjieLog.info('Listening enabled. Data: ' + JSON.stringify(data));
this.updateDisplayInfo();
};
try {
display.on("change", callback);
} catch (exception) {
TuanjieLog.error('Failed to register callback. Code: ' + JSON.stringify(exception));
}
}
private updateDisplayInfo(): void {
let displayClass: display.Display | null = null;
try {
displayClass = display.getDefaultDisplaySync();
this.defaultDisplay.displayID = displayClass.id;
this.defaultDisplay.width = displayClass.width;
this.defaultDisplay.height = displayClass.height;
this.defaultDisplay.rotation = DisplayInfoManager.openHarmonyToNativeRotationMap[displayClass.rotation];
this.defaultDisplay.refreshRate = displayClass.refreshRate;
this.defaultDisplay.densityDPI = displayClass.densityDPI;
AppStorage.setOrCreate<number>(APP_KEY_ORIENTATION_CHANGE, displayClass.rotation);
POST_MESSAGE({
type: 'SetDisplayInfo',
data: this.defaultDisplay
});
}
catch (err) {
TuanjieLog.error('Failed to obtain the default display object. Code: ' + JSON.stringify(err));
}
}
public static getInstance(): DisplayInfoManager {
return DisplayInfoManager.instance;
}
}
import common from '@ohos.app.ability.common';
import { BusinessError } from '@ohos.base';
import fs from '@ohos.file.fs';
import image from '@ohos.multimedia.image';
import photoAccessHelper from '@ohos.file.photoAccessHelper';
import { PickerOptions, ReminderMode, } from '@ohos.file.PhotoPickerComponent';
import { GetFromGlobalThis } from '../common/GlobalThisUtil';
import { REGISTER_BUILTIN_MODULE, MSG_RECEIVER } from '../workers/MessageProcessor';
import { TuanjieLog } from '../common/TuanjieLog';
enum GalleryOperationResult {
Success,
Failure,
Canceled
}
export class GalleryHandler {
public static sandBoxPath: string = "/data/storage/el2/base/haps/entry/files/";
public async GallerySelector(height: number, width: number, maxSelectNumber: number,
isPhotoTakingSupported: boolean): Promise<Array<ESObject>> {
try {
let PhotoSelectOptions: PickerOptions = new PickerOptions();
PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
PhotoSelectOptions.isPhotoTakingSupported = isPhotoTakingSupported;
PhotoSelectOptions.maxSelectedReminderMode = ReminderMode.TOAST;
PhotoSelectOptions.maxSelectNumber = maxSelectNumber;
const photoPicker = new photoAccessHelper.PhotoViewPicker();
const PhotoSelectResult: photoAccessHelper.PhotoSelectResult = await photoPicker.select(PhotoSelectOptions);
const galleryArray: string[] = PhotoSelectResult.photoUris;
const result: Array<ESObject> = await Promise.all(
galleryArray.map((uri) =>
GalleryHandler.handleImage(uri, height, width))
);
return result.flat();
} catch (error) {
TuanjieLog.error('PhotoViewPicker failed with err: ' + error.message);
return [GalleryOperationResult.Failure, null,
'PhotoViewPicker failed with err: ' + error.message];
}
}
public async AddToGallery(fileUris: Array<string>): Promise<Array<ESObject>> {
try {
const context = GetFromGlobalThis('AbilityContext') as common.UIAbilityContext;
const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
let photoCreationConfigs: Array<photoAccessHelper.PhotoCreationConfig> = fileUris.map(
(fileUri): photoAccessHelper.PhotoCreationConfig => {
const pos = fileUri.lastIndexOf('/');
const str = fileUri.substring(pos + 1);
const fileName = str.substring(0, str.lastIndexOf('.'));
const fileExtension = fileUri.substring(fileUri.lastIndexOf('.') + 1);
return {
title: fileName,
fileNameExtension: fileExtension,
photoType: photoAccessHelper.PhotoType.IMAGE,
subtype: photoAccessHelper.PhotoSubtype.DEFAULT,
};
});
const resultPaths = await phAccessHelper.showAssetsCreationDialog(fileUris, photoCreationConfigs);
if (!resultPaths?.length) {
TuanjieLog.error('Show AssetsCreationDialog Canceled.');
return [GalleryOperationResult.Canceled, null,
"Show AssetsCreationDialog canceled."];
}
return await GalleryHandler.handleMultipleImages(fileUris, resultPaths);
} catch (error) {
TuanjieLog.error('Show AssetsCreationDialog failed:' + error.message);
return [GalleryOperationResult.Failure, null,
"Show AssetsCreationDialog failed:" + error.message];
}
}
static async handleImage(uri: string, height: number, width: number): Promise<Array<ESObject>> {
let srcFile: fs.File | undefined = undefined;
let destFile: fs.File | undefined = undefined;
try {
srcFile = fs.openSync(uri, fs.OpenMode.READ_ONLY);
let imageSource: image.ImageSource = image.createImageSource(srcFile.fd);
let decodingOptions: image.DecodingOptions = {
sampleSize: 1,
desiredSize: {
height: height,
width: width
}
};
const pixelMap = await imageSource.createPixelMap(decodingOptions);
const imagePackerApi = image.createImagePacker();
const fileExtension = uri.substring(uri.lastIndexOf('.') + 1);
const imageFormat = fileExtension === 'png' ? 'image/png' : 'image/jpeg';
const packOpts: image.PackingOption = { format: imageFormat, quality: 100 };
const data = await imagePackerApi.packing(pixelMap, packOpts);
const picPath: string = GalleryHandler.sandBoxPath + `${new Date().getTime().toString()}.jpg`;
destFile = fs.openSync(picPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
fs.writeSync(destFile.fd, data);
const result: Array<ESObject> = [GalleryOperationResult.Success, picPath.toString(), null]
return result;
} catch (error) {
TuanjieLog.error("Failed to handleImage: err: %{public}s", error.message);
return [GalleryOperationResult.Failure, null,
"Failed to handleImage: err: " + error.message];
} finally {
if (srcFile) {
fs.closeSync(srcFile);
}
if (destFile) {
fs.closeSync(destFile);
}
}
}
private static async processAndSaveImage(fileUri: string, destPath: string, fileExtension: string): Promise<void> {
let srcFile: fs.File | undefined;
let destFile: fs.File | undefined;
try {
srcFile = fs.openSync(fileUri, fs.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(srcFile.fd);
const decodingOptions: image.DecodingOptions = {
sampleSize: 1,
desiredSize: { height: 0, width: 0 }
};
const pixelMap = await imageSource.createPixelMap(decodingOptions);
const imageFormat = fileExtension === 'png' ? 'image/png' : 'image/jpeg';
const imagePackerApi = image.createImagePacker();
const packOpts: image.PackingOption = { format: imageFormat, quality: 100 };
const data = await imagePackerApi.packing(pixelMap, packOpts);
destFile = fs.openSync(destPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
await fs.write(destFile.fd, data);
} finally {
if (srcFile) {
fs.closeSync(srcFile);
}
if (destFile) {
fs.closeSync(destFile);
}
}
}
private static async handleMultipleImages(fileUris: Array<string>,
resultPaths: Array<string>): Promise<Array<ESObject>> {
const results: Array<ESObject> = [];
for (let i = 0; i < fileUris.length; i++) {
const fileUri = fileUris[i];
const destPath = resultPaths[i];
const pos = fileUri.lastIndexOf('/');
const str = fileUri.substring(pos + 1);
const fileName = str.substring(0, str.lastIndexOf('.'));
const fileExtension = fileUri.substring(fileUri.lastIndexOf('.') + 1);
try {
await GalleryHandler.processAndSaveImage(fileUri, destPath, fileExtension);
results.push([GalleryOperationResult.Success, destPath.toString(), null]);
} catch (error) {
TuanjieLog.error(`Image processing failed for ${fileUri}:` + error.message);
results.push([GalleryOperationResult.Failure, null,
`Image processing failed for ${fileUri}:` + error.message]);
throw error as Error;
}
}
return results.flat();
}
static Instance: GalleryHandler;
public static getInstance(): GalleryHandler {
if (GalleryHandler.Instance == null) {
GalleryHandler.Instance = new GalleryHandler();
}
return GalleryHandler.Instance;
}
}
!!REGISTER_BUILTIN_MODULE && REGISTER_BUILTIN_MODULE(MSG_RECEIVER.HOST_UI, GalleryHandler.getInstance());
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment