精华内容
下载资源
问答
  • DDoS原生防护代播实例可以防护海外云下IDC服务器,或者在云上根据网段实现DDoS防护,适合为海外云下IDC、小型运营商、阿里云海外用户以及有自己BGP网络的用户提供阿里云的DDoS防护,防护过程不需要改变原有业务IP...

    DDoS原生防护代播实例可以防护海外云下IDC服务器,或者在云上根据网段实现DDoS防护,适合为海外云下IDC、小型运营商、阿里云海外用户以及有自己BGP网络的用户提供阿里云的DDoS防护,防护过程不需要改变原有业务IP地址和网络架构。
    >正常流量或小流量攻击:流量直接去向IDC或者访问阿里云DDoS原生防护本地机房,无额外延迟增加,可以防御小流量攻击。
    >发生DDoS攻击时:入流量重定向到阿里云全球Anycast清洗中心,支持多种回注方式返回给用户,流量由全球清洗中心分布式清洗,延迟略有增加,防护能力可以增加到T级别。

    DDoS原生防护代播支持On-demand、always-on两种模式。On-demand在被攻击时,通过控制台手动模式或者API模式重定向流量,适合于攻击相对不是太频繁的业务。always-on模式下,流量一直重定向到清洗中心,适合业务攻击频率较高的业务。如需开通DDoS原生防护代播版,请联系我们。

    更多小知识请关注我持续更新~

    展开全文
  • 第三dplayer播放器记忆+P2P播放+自动下一集功能.zip播放器。
  • 2.2.4 假BGP机房(代播) 目前市面上还有一种更廉价的BGP实现方式,如上图,企业采购中国电信单线机房并广播自身IP地址的同时,与另外一个第三方IDC服务商的伪BGP机房进行专线互联,同时将自己的IP地址在第三方IDC...

    一. 什么是BGP
    二. 具体实现方案
            2.1BGP的优点
            2.2 真伪BGP在使用效果上有什么差异
    ​​​​​​​        ​​​​​​​        2.2.1 真BGP实现了用户最佳路径的自动选择​
    ​​​​​​​        ​​​​​​​        2.2.2 伪BGP不具备真BGP动态最佳路径切换​
    ​​​​​​​        ​​​​​​​        2.2.3 半真半假的BGP机房​
    ​​​​​​​        ​​​​​​​        2.2.4 假BGP机房(代播)​
    三. BGP 穿透方式
    ​​​​​​​        ​​​​​​​3.1 全穿透(真BGP)
    ​​​​​​​        3.2 半穿透(静态BGP)
    ​​​​​​​        ​​​​​​​3.3 非穿透(静态代播BGP,假BGP)


    一. 什么是BGP?

        边界网关协议(BGP)是运行于 TCP 上的一种自治系统(AS)的路由协议,是唯一能够妥善处理不相关路由域间的多路连接的协议。

    通俗点讲:中国电信 、中国联通、中国移动和一些拥有AS自治域的大型民营IDC运营商就可以通过BGP协议来实现多线互联。

        比如:出差去上海(网站) ,可以选择公路(电信)飞机(联通)、高铁(移动)等多种方式。BGP就好比一个智能APP可以帮助用户选择最佳的交通形式,并且可以在出现临时问题时,自动帮用户选择最佳的交通方式。

    二. 具体实现方案

        国内 IDC 机房需要在 CNNIC(中国互联网信息中心) APNIC(亚太网络信息中心) 申请自己的 IP 地址段和 AS 号,然后将自己的 IP 地址广播到其它网络运营商的 AS 中,并通过BGP协议将多个AS进行连接,从而实现可自动跨网访问。
        此时,当用户发出访问请求后,将根据BGP协议的机制自动在已建立连接的多个AS之间为用户提供最佳路由,从而实现不同网络运营商用户的高速访问同一机房资源。

    2.1BGP的优点:

        自动最优网络路径的选择,线路冗余,网络可靠稳定,实现多网接入。

    2.2 真伪BGP在使用效果上有什么差异

    2.2.1 真BGP实现了用户最佳路径的自动选择

    即使遇到单条线路故障,BGP依然可以自动引导用户选择最佳的访问路径

    2.2.2 伪BGP不具备真BGP动态最佳路径切换

    伪BGP虽然也实现了多线接入,但并不具备真BGP动态最佳路径切换的功能。因此,一旦线路出现故障就会造成用户无法访问的情况,只能通过人工手动切换访问线路。

    2.2.3 半真半假的BGP机房

       比如一个号称 五线 BGP的机房,其中联通、移动、A企业为BGP动态带宽,而中国电信和B企业为静态带宽,一旦联通或其它动态带宽线路出现故障,用户只可以自动被分配到移动和A企业的动态带宽线路上,依然实现了BGP的效果,但中国电信或B企业的静态带宽出现故障,则用户不会被自动分配到其他静态或动态网路中,并在静态带宽所在AS中形成路由黑洞,该AS内的用户将出现无法访问情况,需要人为介入进行手动修复且修复时间较长。

    2.2.4 假BGP机房(代播)

        目前市面上还有一种更廉价的BGP实现方式,如上图,企业采购中国电信单线机房并广播自身IP地址的同时,与另外一个第三方IDC服务商的伪BGP机房进行专线互联,同时将自己的IP地址在第三方IDC服务商AS域中进行广播(代播),这样就可以变相的在单线机房基础上实现了BGP效果,但同上一案例一样,一旦出现静态带宽故障,路由黑洞出现,必须人工进行介入,且修复时间较长。

    三. BGP 穿透方式

    3.1 全穿透(真BGP)

        全穿透,其实就是和2个以上的AS连接,并且允许其他AS数据穿过本AS。比如,A-B-C,3个AS,B就是一个穿透AS,允许AC的数据从B中通过。
        简单说,全穿透BGP指的是能实现跨运营商进行互联,各运营商之间可以互作冗余的动态BGP带宽,属于中立带宽厂商,三大运营商之间可以互作冗余,自动择优选择路径,相当于汽车的自动档、及配有导航功能,全穿透动态BGP带宽是优于独立运营商动态BGP带宽的一种带宽。全穿透动态BGP带宽可用率更高可达到 99.999999%

    3.2 半穿透(静态BGP)

        半穿透,指的是运营商自己网内可以实现的动态BGP带宽,如电信动态BGP带宽、联通动态BGP带宽、移动动态BGP带宽,也只能在北上广才可以申请到,普通二三线城市没有动态BGP带宽。当地运营商不支持申请开通。
        这种半穿透动态BGP带宽只能满足本网网内的路由切换,不能跨运营商进行冗余,所以这种单个运营商网内的动态BGP的可用率只能满足99.99%。

    3.3 非穿透(静态代播BGP,假BGP)

        非穿透,指AS外出只有1条路径,或者说本AS只和1个其他AS连接,本AS的外出全部通过直连AS。这种又叫做静态代播BGP,因为这种带宽运营厂商没有自己的AS号,IP地址找运营商代播的,这种带宽不具备BGP的功能,也叫普通静态BGP.
        普通BGP二线、普通BGP三线、普通BGP四线、普通BGP多线等,显示效果是多线单IP,但实际上他的路由表就固定几条,当某一个运营商出现故障时,就会出现某个运营商某个城市出口方向故障,导致部分用户无法访问的情况。只能等人工去解决,不能自动修复。这种静态代播BGP的带宽可用率只能达到 99%

    展开全文
  • jQuery Gif图片播放和暂停插件是一款使用一张静态的图片作为GIF图片的预览图,然后在用户点击播放按钮时才开始GIF动画序列的播放
  • nginx代理服务器,将rtsp转换成rtmp,提供浏览器播放视频流,可用,利于开发测试,很好的工具
  • 创建基于官方第三人称模版C++工程,项目名字:MyTP,以下例子基于该工程实现 代理委托就是函数指针(类成员函数指针),函数指针指向函数地址,然后调用该函数指针...1、代理声明、绑定、执行(单) MyTPChara...

    官方例子https://docs.unrealengine.com/en-us/Programming/UnrealArchitecture/Delegates

    创建基于官方第三人称模版C++工程,项目名字:MyTP,以下例子基于该工程实现

    代理委托就是函数指针(类成员函数指针),函数指针指向函数地址,然后调用该函数指针,实现所需效果。

    1、代理声明、绑定、执行(单播)

    MyTPCharacter.h :

     

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Character.h"
    #include "MyTPCharacter.generated.h"
    
    
    //声明无参代理
    DECLARE_DELEGATE(D_NoParam)
    //声明1个参数的代理
    DECLARE_DELEGATE_OneParam(D_1Param, FString)
    //声明5个参数的代理
    DECLARE_DELEGATE_FiveParams(D_5Param, FString, FString, FString, FString, FString)
    //返回值代理
    DECLARE_DELEGATE_RetVal(FString, D_RV)
    
    
    UCLASS(config=Game)
    class AMyTPCharacter : public ACharacter
    {
    	GENERATED_BODY()
    
    	/** Camera boom positioning the camera behind the character */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    	class USpringArmComponent* CameraBoom;
    
    	/** Follow camera */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    	class UCameraComponent* FollowCamera;
    public:
    	AMyTPCharacter();
    
    	/** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
    	float BaseTurnRate;
    
    	/** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
    	float BaseLookUpRate;
    
    
    	//定义代理
    	D_NoParam DelegateNoParam;
    	D_1Param Delegate1Param;
    	D_5Param Delegate5Param;
    	D_RV DelegateRV;
    
    	//绑定到代理的函数
    	void FuncNoParam();
    	void Func1Param(FString Str);
    	void Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5);
    	FString FuncRV();
    
    
    	void OnRunEvent();//绑定到Q键函数,执行所有代理
    	void PrintScreenStr(FString Str);//打印日志到屏幕函数
    
    protected:
    
    	/** Resets HMD orientation in VR. */
    	void OnResetVR();
    
    	/** Called for forwards/backward input */
    	void MoveForward(float Value);
    
    	/** Called for side to side input */
    	void MoveRight(float Value);
    
    	/** 
    	 * Called via input to turn at a given rate. 
    	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
    	 */
    	void TurnAtRate(float Rate);
    
    	/**
    	 * Called via input to turn look up/down at a given rate. 
    	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
    	 */
    	void LookUpAtRate(float Rate);
    
    	/** Handler for when a touch input begins. */
    	void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);
    
    	/** Handler for when a touch input stops. */
    	void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);
    
    protected:
    	// APawn interface
    	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
    	// End of APawn interface
    
    public:
    	/** Returns CameraBoom subobject **/
    	FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
    	/** Returns FollowCamera subobject **/
    	FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
    };
    
    

    MyTPCharacter.cpp:

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
    #include "MyTPCharacter.h"
    #include "HeadMountedDisplayFunctionLibrary.h"
    #include "Camera/CameraComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "Components/InputComponent.h"
    #include "GameFramework/CharacterMovementComponent.h"
    #include "GameFramework/Controller.h"
    #include "GameFramework/SpringArmComponent.h"
    
    #include "Engine/GameEngine.h"
    
    
    //
    // AMyTPCharacter
    
    AMyTPCharacter::AMyTPCharacter()
    {
    	
    	 DelegateNoParam.BindUObject(this,&AMyTPCharacter::FuncNoParam);
    	 Delegate1Param.BindUObject(this, &AMyTPCharacter::Func1Param);
    	 Delegate5Param.BindUObject(this, &AMyTPCharacter::Func5Param);
    	 DelegateRV.BindUObject(this, &AMyTPCharacter::FuncRV);
    	
    	// Set size for collision capsule
    	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
    
    	// set our turn rates for input
    	BaseTurnRate = 45.f;
    	BaseLookUpRate = 45.f;
    
    	// Don't rotate when the controller rotates. Let that just affect the camera.
    	bUseControllerRotationPitch = false;
    	bUseControllerRotationYaw = false;
    	bUseControllerRotationRoll = false;
    
    	// Configure character movement
    	GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...	
    	GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate
    	GetCharacterMovement()->JumpZVelocity = 600.f;
    	GetCharacterMovement()->AirControl = 0.2f;
    
    	// Create a camera boom (pulls in towards the player if there is a collision)
    	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    	CameraBoom->SetupAttachment(RootComponent);
    	CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character	
    	CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller
    
    	// Create a follow camera
    	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    	FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
    	FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
    
    	// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) 
    	// are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)
    }
    
    //
    // Input
    
    void AMyTPCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
    {
    	// Set up gameplay key bindings
    	check(PlayerInputComponent);
    
    	PlayerInputComponent->BindAction("RunEvent", IE_Pressed, this, &AMyTPCharacter::OnRunEvent);
    
    
    
    	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
    	PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
    
    	PlayerInputComponent->BindAxis("MoveForward", this, &AMyTPCharacter::MoveForward);
    	PlayerInputComponent->BindAxis("MoveRight", this, &AMyTPCharacter::MoveRight);
    
    	// We have 2 versions of the rotation bindings to handle different kinds of devices differently
    	// "turn" handles devices that provide an absolute delta, such as a mouse.
    	// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
    	PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
    	PlayerInputComponent->BindAxis("TurnRate", this, &AMyTPCharacter::TurnAtRate);
    	PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
    	PlayerInputComponent->BindAxis("LookUpRate", this, &AMyTPCharacter::LookUpAtRate);
    
    	// handle touch devices
    	PlayerInputComponent->BindTouch(IE_Pressed, this, &AMyTPCharacter::TouchStarted);
    	PlayerInputComponent->BindTouch(IE_Released, this, &AMyTPCharacter::TouchStopped);
    
    	// VR headset functionality
    	PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &AMyTPCharacter::OnResetVR);
    
    	
    }
    
    
    void AMyTPCharacter::PrintScreenStr(FString Str)
    {
    	if (GEngine) 
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Red, Str);
    	}
    }
    
    void AMyTPCharacter::OnRunEvent()
    {
    	DelegateNoParam.ExecuteIfBound();
    	Delegate1Param.ExecuteIfBound("Delegate1Param!");
    	Delegate5Param.ExecuteIfBound("1Param!", "2Param!", "3Param!", "4Param!", "5Param!");
    	FString CurRV = DelegateRV.Execute();
    	PrintScreenStr(CurRV);
    	
    }
    void AMyTPCharacter::FuncNoParam()
    {
    	PrintScreenStr("NoParam");
    }
    void AMyTPCharacter::Func1Param(FString Str)
    {
    	PrintScreenStr(Str);
    }
    void AMyTPCharacter::Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5)
    {
    	PrintScreenStr(Str1 + " "+ Str2 + " " + Str3 +" "+ Str4 + " " + Str5);
    }
    FString AMyTPCharacter::FuncRV()
    {
    	return  FString("FuncRV");
    }
    
    void AMyTPCharacter::OnResetVR()
    {
    	UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
    }
    
    void AMyTPCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
    {
    		Jump();
    }
    
    void AMyTPCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
    {
    		StopJumping();
    }
    
    void AMyTPCharacter::TurnAtRate(float Rate)
    {
    	// calculate delta for this frame from the rate information
    	AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
    }
    
    void AMyTPCharacter::LookUpAtRate(float Rate)
    {
    	// calculate delta for this frame from the rate information
    	AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
    }
    
    void AMyTPCharacter::MoveForward(float Value)
    {
    	if ((Controller != NULL) && (Value != 0.0f))
    	{
    		// find out which way is forward
    		const FRotator Rotation = Controller->GetControlRotation();
    		const FRotator YawRotation(0, Rotation.Yaw, 0);
    
    		// get forward vector
    		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
    		AddMovementInput(Direction, Value);
    	}
    }
    
    void AMyTPCharacter::MoveRight(float Value)
    {
    	if ( (Controller != NULL) && (Value != 0.0f) )
    	{
    		// find out which way is right
    		const FRotator Rotation = Controller->GetControlRotation();
    		const FRotator YawRotation(0, Rotation.Yaw, 0);
    	
    		// get right vector 
    		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
    		// add movement in that direction
    		AddMovementInput(Direction, Value);
    	}
    }
    

    绑定Q键如下图:

    启动 游戏,摁下Q键盘,屏幕信息如下:

    2、代理各种绑定(单播)

    还是基于步骤1,编辑器创建一个基于None的类,名字:MyTest

    MyTest.h

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    
    /**
     * 
     */
    class MYTP_API MyTest
    {
    public:
    	MyTest();
    	~MyTest();
    	void TestFunc(FString Str);
    
    };
    

    MyTest.cpp

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "MyTest.h"
    #include "Engine/GameEngine.h"
    
    MyTest::MyTest()
    {
    }
    
    MyTest::~MyTest()
    {
    }
    void MyTest::TestFunc(FString Str)
    {
    	if (GEngine) 
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, Str);
    	}
    }

    MyTPCharacter.h :

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Character.h"
    #include "MyTest.h"
    #include "MyTPCharacter.generated.h"
    
    
    
    //声明无参代理
    DECLARE_DELEGATE(D_NoParam)
    //声明1个参数的代理
    DECLARE_DELEGATE_OneParam(D_1Param, FString)
    //声明5个参数的代理
    DECLARE_DELEGATE_FiveParams(D_5Param, FString, FString, FString, FString, FString)
    //返回值代理
    DECLARE_DELEGATE_RetVal(FString, D_RV)
    
    
    static void Static_Func(FString Str)
    {
    	if (GEngine)
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, Str);
    	}
    }
    
    
    
    UCLASS(config=Game)
    class AMyTPCharacter : public ACharacter
    {
    	GENERATED_BODY()
    
    	/** Camera boom positioning the camera behind the character */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    	class USpringArmComponent* CameraBoom;
    
    	/** Follow camera */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    	class UCameraComponent* FollowCamera;
    public:
    	AMyTPCharacter();
    
    	/** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
    	float BaseTurnRate;
    
    	/** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
    	float BaseLookUpRate;
    
    
    	//定义代理
    	D_NoParam DelegateNoParam;
    	D_1Param Delegate1Param;
    	D_5Param Delegate5Param;
    	D_RV DelegateRV;
    
    	//绑定到代理的函数
    	void FuncNoParam();
    	void Func1Param(FString Str);
    	void Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5);
    	FString FuncRV();
    
    	void OnRunEvent();//绑定到Q键函数,执行所有代理
    	void PrintScreenStr(FString Str);//打印日志到屏幕函数
    
    	//定义多种绑定
    	D_1Param DelegateLambda;
    	D_1Param DelegateRaw;
    	D_1Param DelegateSP;
    	D_1Param DelegateStatic;
    	D_1Param DelegateUFunction;
    
    	UFUNCTION()//可以声明BlueprintCallable等其他标记
    		void TestUFunc(FString str);
    	MyTest MyT;
    	TSharedPtr<MyTest> MyT_SPtr;
    	
    protected:
    
    	/** Resets HMD orientation in VR. */
    	void OnResetVR();
    
    	/** Called for forwards/backward input */
    	void MoveForward(float Value);
    
    	/** Called for side to side input */
    	void MoveRight(float Value);
    
    	/** 
    	 * Called via input to turn at a given rate. 
    	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
    	 */
    	void TurnAtRate(float Rate);
    
    	/**
    	 * Called via input to turn look up/down at a given rate. 
    	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
    	 */
    	void LookUpAtRate(float Rate);
    
    	/** Handler for when a touch input begins. */
    	void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);
    
    	/** Handler for when a touch input stops. */
    	void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);
    
    protected:
    	// APawn interface
    	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
    	// End of APawn interface
    
    public:
    	/** Returns CameraBoom subobject **/
    	FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
    	/** Returns FollowCamera subobject **/
    	FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
    };
    
    

    MyTPCharacter.cpp:

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
    #include "MyTPCharacter.h"
    #include "HeadMountedDisplayFunctionLibrary.h"
    #include "Camera/CameraComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "Components/InputComponent.h"
    #include "GameFramework/CharacterMovementComponent.h"
    #include "GameFramework/Controller.h"
    #include "GameFramework/SpringArmComponent.h"
    
    #include "Engine/GameEngine.h"
    #include "MyTest.h"
    
    //
    // AMyTPCharacter
    
    AMyTPCharacter::AMyTPCharacter()
    {
    	
    	// Set size for collision capsule
    	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
    
    	// set our turn rates for input
    	BaseTurnRate = 45.f;
    	BaseLookUpRate = 45.f;
    
    	// Don't rotate when the controller rotates. Let that just affect the camera.
    	bUseControllerRotationPitch = false;
    	bUseControllerRotationYaw = false;
    	bUseControllerRotationRoll = false;
    
    	// Configure character movement
    	GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...	
    	GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate
    	GetCharacterMovement()->JumpZVelocity = 600.f;
    	GetCharacterMovement()->AirControl = 0.2f;
    
    	// Create a camera boom (pulls in towards the player if there is a collision)
    	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    	CameraBoom->SetupAttachment(RootComponent);
    	CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character	
    	CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller
    
    	// Create a follow camera
    	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    	FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
    	FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
    
    	// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) 
    	// are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)
    
    
    	DelegateNoParam.BindUObject(this, &AMyTPCharacter::FuncNoParam);//this只能是继承自UObject的类,绑定继承UObject类的对象函数
    	Delegate1Param.BindUObject(this, &AMyTPCharacter::Func1Param);
    	Delegate5Param.BindUObject(this, &AMyTPCharacter::Func5Param);
    	DelegateRV.BindUObject(this, &AMyTPCharacter::FuncRV);
    
    
    	//Lambda表达式
    	auto LambdaFunc = [&](FString str)
    	{
    		if (GEngine) {
    			GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, str);
    		}
    	};
    	DelegateLambda.BindLambda(LambdaFunc);//绑定lambda表达式
    	DelegateRaw.BindRaw(&MyT, &MyTest::TestFunc);//绑定到一个原始的C++类函数绑定到一个原始的C++指针全局函数代理上。原始指针不使用任何引用,所以如果从代理的底层删除了该对象,那么调用它可能是不安全的。因此,当调用Execute()时一定要小心!
    
    	
    	MyT_SPtr = MakeShareable(new MyTest());
    	DelegateSP.BindSP(MyT_SPtr.ToSharedRef(), &MyTest::TestFunc);//绑定一个基于共享指针的成员函数代理。共享指针代理保持到您的对象的弱引用。您可以使用 ExecuteIfBound() 来调用它们。
    	DelegateStatic.BindStatic(Static_Func);//绑定全局静态函数
    	DelegateUFunction.BindUFunction(this,"TestUFunc");//绑定this指向对象类蓝图函数
    
    }
    
    //
    // Input
    
    void AMyTPCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
    {
    	// Set up gameplay key bindings
    	check(PlayerInputComponent);
    
    	PlayerInputComponent->BindAction("RunEvent", IE_Pressed, this, &AMyTPCharacter::OnRunEvent);
    
    
    
    	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
    	PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
    
    	PlayerInputComponent->BindAxis("MoveForward", this, &AMyTPCharacter::MoveForward);
    	PlayerInputComponent->BindAxis("MoveRight", this, &AMyTPCharacter::MoveRight);
    
    	// We have 2 versions of the rotation bindings to handle different kinds of devices differently
    	// "turn" handles devices that provide an absolute delta, such as a mouse.
    	// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
    	PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
    	PlayerInputComponent->BindAxis("TurnRate", this, &AMyTPCharacter::TurnAtRate);
    	PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
    	PlayerInputComponent->BindAxis("LookUpRate", this, &AMyTPCharacter::LookUpAtRate);
    
    	// handle touch devices
    	PlayerInputComponent->BindTouch(IE_Pressed, this, &AMyTPCharacter::TouchStarted);
    	PlayerInputComponent->BindTouch(IE_Released, this, &AMyTPCharacter::TouchStopped);
    
    	// VR headset functionality
    	PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &AMyTPCharacter::OnResetVR);
    
    	
    }
    
    
    void AMyTPCharacter::PrintScreenStr(FString Str)
    {
    	if (GEngine) 
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Red, Str);
    	}
    }
    
    void AMyTPCharacter::OnRunEvent()
    {
    	DelegateNoParam.ExecuteIfBound();
    	Delegate1Param.ExecuteIfBound("Delegate1Param!");
    	Delegate5Param.ExecuteIfBound("1Param!", "2Param!", "3Param!", "4Param!", "5Param!");
    	FString CurRV = DelegateRV.Execute();
    	PrintScreenStr(CurRV);
    
    	 DelegateLambda.ExecuteIfBound("DelegateLambda!");
    	 DelegateRaw.ExecuteIfBound("DelegateRaw!");
    	 DelegateSP.ExecuteIfBound("DelegateSP!");
    	 DelegateStatic.ExecuteIfBound("DelegateStatic!");
    	 DelegateUFunction.ExecuteIfBound("DelegateUFunction!");
    	
    }
    void AMyTPCharacter::FuncNoParam()
    {
    	PrintScreenStr("NoParam");
    }
    void AMyTPCharacter::Func1Param(FString Str)
    {
    	PrintScreenStr(Str);
    }
    void AMyTPCharacter::Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5)
    {
    	PrintScreenStr(Str1 + " "+ Str2 + " " + Str3 +" "+ Str4 + " " + Str5);
    }
    FString AMyTPCharacter::FuncRV()
    {
    	return  FString("FuncRV");
    }
    
    void AMyTPCharacter::TestUFunc(FString str)
    {
    	if (GEngine)
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, str);
    	}
    }
    
    
    
    
    
    void AMyTPCharacter::OnResetVR()
    {
    	UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
    }
    
    
    
    void AMyTPCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
    {
    		Jump();
    }
    
    void AMyTPCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
    {
    		StopJumping();
    }
    
    void AMyTPCharacter::TurnAtRate(float Rate)
    {
    	// calculate delta for this frame from the rate information
    	AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
    }
    
    void AMyTPCharacter::LookUpAtRate(float Rate)
    {
    	// calculate delta for this frame from the rate information
    	AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
    }
    
    void AMyTPCharacter::MoveForward(float Value)
    {
    	if ((Controller != NULL) && (Value != 0.0f))
    	{
    		// find out which way is forward
    		const FRotator Rotation = Controller->GetControlRotation();
    		const FRotator YawRotation(0, Rotation.Yaw, 0);
    
    		// get forward vector
    		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
    		AddMovementInput(Direction, Value);
    	}
    }
    
    void AMyTPCharacter::MoveRight(float Value)
    {
    	if ( (Controller != NULL) && (Value != 0.0f) )
    	{
    		// find out which way is right
    		const FRotator Rotation = Controller->GetControlRotation();
    		const FRotator YawRotation(0, Rotation.Yaw, 0);
    	
    		// get right vector 
    		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
    		// add movement in that direction
    		AddMovementInput(Direction, Value);
    	}
    }
    

    按Q键,运行输出到屏幕如下图:

    3、多播代理委托

    多播代理的功能和单播代理几乎一样。区别是它们对目标为弱引用,可以和结构体一起使用,可以很方便地进行拷贝,等等。 和普通代理一样,多播代理可被载入/保存,并远程触发;但是,多播代理的函数无法使用返回值。最好将它们用于方便地传递代理集合。事件 是特定类型的多播代理,同时具有对Broadcast(), IsBound(), 和 Clear()函数的受限访问。

    多播代理在代理触发时可能会调用多个函数绑定。因此,绑定函数在语句中看起来更为像是数组。

    多播代理允许您附加多个函数代理,然后通过调用多播代理的 Broadcast() 函数一次性执行所有函数代理。多播代理的签名不能使用返回值。

    任何时候在多播代理上调用 Broadcast() 函数都是安全的,即时它没有绑定任何函数也可以。唯一需要注意的时候是您使用代理初始化输出变量时,这样做一般是非常不好的。

    当调用 Broadcast() 函数时,绑定函数的执行顺序是不确定的。可能并不按照函数的添加顺序执行

    步骤1和2单播代理委托,指的是只能绑定一个函数指针的委托,实现一对一的通知。
    多播代理委托,指的是能绑定多个函数指针的委托,实现一对多的通知。
    多播代理委托的定义是有“MULTICAST”修饰的委托

    继续基于步骤1、2实现,在编辑器创建基于C++类继承于Actor两个类,名字分别叫做:TestActor、TestActor2

    TestActor.h:

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "TestActor.generated.h"
    
    UCLASS()
    class MYTP_API ATestActor : public AActor
    {
    	GENERATED_BODY()
    	
    public:	
    	// Sets default values for this actor's properties
    	ATestActor();
    
    protected:
    	// Called when the game starts or when spawned
    	virtual void BeginPlay() override;
    
    public:	
    	// Called every frame
    	virtual void Tick(float DeltaTime) override;
    
    	void FuncNoParam();
    	void Func1Param(FString str);
    
    	
    };
    

    TestActor.cpp:

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "TestActor.h"
    #include "Engine/GameEngine.h"
    #include "MyTPCharacter.h"
    
    // Sets default values
    ATestActor::ATestActor()
    {
     	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    	PrimaryActorTick.bCanEverTick = true;
    
    }
    
    // Called when the game starts or when spawned
    void ATestActor::BeginPlay()
    {
    	Super::BeginPlay();
    
    	APlayerController* PC = GetWorld()->GetFirstPlayerController();
    	if (nullptr == PC)
    		return;
    	ACharacter* Cha = PC->GetCharacter();
    	if (nullptr == Cha)
    		return;
    	AMyTPCharacter* MyTPC = Cast<AMyTPCharacter>(Cha);
    	if (nullptr == MyTPC)
    		return;
    
    	MyTPC->DelegateMulticastD_NoParam.AddUObject(this, &ATestActor::FuncNoParam);
    	MyTPC->DelegateMulticastD_1Param.AddUObject(this, &ATestActor::Func1Param);
    
    
    }
    
    // Called every frame
    void ATestActor::Tick(float DeltaTime)
    {
    	Super::Tick(DeltaTime);
    
    }
    
    void ATestActor::FuncNoParam()
    {
    	if (GEngine)
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Yellow, "TestActor FuncNoParam");
    	}
    }
    void ATestActor::Func1Param(FString str)
    {
    	if (GEngine)
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Yellow, "TestActor Func1Param"+str);
    	}
    }

    TestActor2.h;

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "TestActor2.generated.h"
    
    UCLASS()
    class MYTP_API ATestActor2 : public AActor
    {
    	GENERATED_BODY()
    	
    public:	
    	// Sets default values for this actor's properties
    	ATestActor2();
    
    protected:
    	// Called when the game starts or when spawned
    	virtual void BeginPlay() override;
    
    public:	
    	// Called every frame
    	virtual void Tick(float DeltaTime) override;
    
    	void FuncNoParam();
    	void Func1Param(FString str);
    
    	
    };
    

    TestActor2.cpp

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "TestActor2.h"
    #include "Engine/GameEngine.h"
    #include "MyTPCharacter.h"
    
    // Sets default values
    ATestActor2::ATestActor2()
    {
     	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    	PrimaryActorTick.bCanEverTick = true;
    
    }
    
    // Called when the game starts or when spawned
    void ATestActor2::BeginPlay()
    {
    	Super::BeginPlay();
    
    	APlayerController* PC = GetWorld()->GetFirstPlayerController();
    	if (nullptr == PC)
    		return;
    	ACharacter* Cha = PC->GetCharacter();
    	if (nullptr == Cha)
    		return;
    	AMyTPCharacter* MyTPC = Cast<AMyTPCharacter>(Cha);
    	if (nullptr == MyTPC)
    		return;
    
    	MyTPC->DelegateMulticastD_NoParam.AddUObject(this, &ATestActor2::FuncNoParam);
    	MyTPC->DelegateMulticastD_1Param.AddUObject(this, &ATestActor2::Func1Param);
    
    }
    
    // Called every frame
    void ATestActor2::Tick(float DeltaTime)
    {
    	Super::Tick(DeltaTime);
    
    }
    
    void ATestActor2::FuncNoParam()
    {
    	if (GEngine)
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Yellow, "TestActor2 FuncNoParam");
    	}
    }
    void ATestActor2::Func1Param(FString str)
    {
    	if (GEngine)
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Yellow, "TestActor2 Func1Param" + str);
    	}
    }

    MyTPCharacter.h:

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Character.h"
    #include "MyTest.h"
    #include "MyTPCharacter.generated.h"
    
    
    
    //声明无参代理
    DECLARE_DELEGATE(D_NoParam)
    //声明1个参数的代理
    DECLARE_DELEGATE_OneParam(D_1Param, FString)
    //声明5个参数的代理
    DECLARE_DELEGATE_FiveParams(D_5Param, FString, FString, FString, FString, FString)
    //返回值代理
    DECLARE_DELEGATE_RetVal(FString, D_RV)
    
    static void Static_Func(FString Str)
    {
    	if (GEngine)
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, Str);
    	}
    }
    
    //声明多播委托,与单播委托一样支持多参数传入,动态多播代理的名称开头须为F,否则会编译报错
    DECLARE_MULTICAST_DELEGATE(FMulticastD_NoParam);//声明无参数多播 
    DECLARE_MULTICAST_DELEGATE_OneParam(FMulticastD_1Param, FString);//声明一个参数多播
    
    
    
    UCLASS(config=Game)
    class AMyTPCharacter : public ACharacter
    {
    	GENERATED_BODY()
    
    	/** Camera boom positioning the camera behind the character */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    	class USpringArmComponent* CameraBoom;
    
    	/** Follow camera */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    	class UCameraComponent* FollowCamera;
    public:
    	AMyTPCharacter();
    
    	/** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
    	float BaseTurnRate;
    
    	/** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
    	float BaseLookUpRate;
    
    
    	//定义代理
    	D_NoParam DelegateNoParam;
    	D_1Param Delegate1Param;
    	D_5Param Delegate5Param;
    	D_RV DelegateRV;
    
    	//绑定到代理的函数
    	void FuncNoParam();
    	void Func1Param(FString Str);
    	void Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5);
    	FString FuncRV();
    
    	void OnRunEvent();//绑定到Q键函数,执行所有代理
    	void PrintScreenStr(FString Str);//打印日志到屏幕函数
    
    	//定义多种绑定
    	D_1Param DelegateLambda;
    	D_1Param DelegateRaw;
    	D_1Param DelegateSP;
    	D_1Param DelegateStatic;
    	D_1Param DelegateUFunction;
    
    	UFUNCTION()//可以声明BlueprintCallable等其他标记
    		void TestUFunc(FString str);
    	MyTest MyT;
    	TSharedPtr<MyTest> MyT_SPtr;
    
    
    	//多播
    	FMulticastD_NoParam DelegateMulticastD_NoParam;
    	FMulticastD_1Param  DelegateMulticastD_1Param;
    
    
    protected:
    
    	/** Resets HMD orientation in VR. */
    	void OnResetVR();
    
    	/** Called for forwards/backward input */
    	void MoveForward(float Value);
    
    	/** Called for side to side input */
    	void MoveRight(float Value);
    
    	/** 
    	 * Called via input to turn at a given rate. 
    	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
    	 */
    	void TurnAtRate(float Rate);
    
    	/**
    	 * Called via input to turn look up/down at a given rate. 
    	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
    	 */
    	void LookUpAtRate(float Rate);
    
    	/** Handler for when a touch input begins. */
    	void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);
    
    	/** Handler for when a touch input stops. */
    	void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);
    
    protected:
    	// APawn interface
    	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
    	// End of APawn interface
    
    public:
    	/** Returns CameraBoom subobject **/
    	FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
    	/** Returns FollowCamera subobject **/
    	FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
    };
    
    

    MyTPCharacter.cpp:

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
    #include "MyTPCharacter.h"
    #include "HeadMountedDisplayFunctionLibrary.h"
    #include "Camera/CameraComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "Components/InputComponent.h"
    #include "GameFramework/CharacterMovementComponent.h"
    #include "GameFramework/Controller.h"
    #include "GameFramework/SpringArmComponent.h"
    
    #include "Engine/GameEngine.h"
    #include "MyTest.h"
    
    //
    // AMyTPCharacter
    
    AMyTPCharacter::AMyTPCharacter()
    {
    	
    	// Set size for collision capsule
    	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
    
    	// set our turn rates for input
    	BaseTurnRate = 45.f;
    	BaseLookUpRate = 45.f;
    
    	// Don't rotate when the controller rotates. Let that just affect the camera.
    	bUseControllerRotationPitch = false;
    	bUseControllerRotationYaw = false;
    	bUseControllerRotationRoll = false;
    
    	// Configure character movement
    	GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...	
    	GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate
    	GetCharacterMovement()->JumpZVelocity = 600.f;
    	GetCharacterMovement()->AirControl = 0.2f;
    
    	// Create a camera boom (pulls in towards the player if there is a collision)
    	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    	CameraBoom->SetupAttachment(RootComponent);
    	CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character	
    	CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller
    
    	// Create a follow camera
    	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    	FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
    	FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
    
    	// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) 
    	// are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)
    
    
    	DelegateNoParam.BindUObject(this, &AMyTPCharacter::FuncNoParam);//this只能是继承自UObject的类,绑定继承UObject类的对象函数
    	Delegate1Param.BindUObject(this, &AMyTPCharacter::Func1Param);
    	Delegate5Param.BindUObject(this, &AMyTPCharacter::Func5Param);
    	DelegateRV.BindUObject(this, &AMyTPCharacter::FuncRV);
    
    
    	//Lambda表达式
    	auto LambdaFunc = [&](FString str)
    	{
    		if (GEngine) {
    			GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, str);
    		}
    	};
    	DelegateLambda.BindLambda(LambdaFunc);//绑定lambda表达式
    	DelegateRaw.BindRaw(&MyT, &MyTest::TestFunc);//绑定到一个原始的C++类函数绑定到一个原始的C++指针全局函数代理上。原始指针不使用任何引用,所以如果从代理的底层删除了该对象,那么调用它可能是不安全的。因此,当调用Execute()时一定要小心!
    
    	
    	MyT_SPtr = MakeShareable(new MyTest());
    	DelegateSP.BindSP(MyT_SPtr.ToSharedRef(), &MyTest::TestFunc);//绑定一个基于共享指针的成员函数代理。共享指针代理保持到您的对象的弱引用。您可以使用 ExecuteIfBound() 来调用它们。
    	DelegateStatic.BindStatic(Static_Func);//绑定全局静态函数
    	DelegateUFunction.BindUFunction(this,"TestUFunc");//绑定this指向对象类蓝图函数
    
    }
    
    //
    // Input
    
    void AMyTPCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
    {
    	// Set up gameplay key bindings
    	check(PlayerInputComponent);
    
    	PlayerInputComponent->BindAction("RunEvent", IE_Pressed, this, &AMyTPCharacter::OnRunEvent);
    
    
    
    	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
    	PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
    
    	PlayerInputComponent->BindAxis("MoveForward", this, &AMyTPCharacter::MoveForward);
    	PlayerInputComponent->BindAxis("MoveRight", this, &AMyTPCharacter::MoveRight);
    
    	// We have 2 versions of the rotation bindings to handle different kinds of devices differently
    	// "turn" handles devices that provide an absolute delta, such as a mouse.
    	// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
    	PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
    	PlayerInputComponent->BindAxis("TurnRate", this, &AMyTPCharacter::TurnAtRate);
    	PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
    	PlayerInputComponent->BindAxis("LookUpRate", this, &AMyTPCharacter::LookUpAtRate);
    
    	// handle touch devices
    	PlayerInputComponent->BindTouch(IE_Pressed, this, &AMyTPCharacter::TouchStarted);
    	PlayerInputComponent->BindTouch(IE_Released, this, &AMyTPCharacter::TouchStopped);
    
    	// VR headset functionality
    	PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &AMyTPCharacter::OnResetVR);
    
    	
    }
    
    
    void AMyTPCharacter::PrintScreenStr(FString Str)
    {
    	if (GEngine) 
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Red, Str);
    	}
    }
    
    void AMyTPCharacter::OnRunEvent()
    {
    	//执行单播
    	DelegateNoParam.ExecuteIfBound();
    	Delegate1Param.ExecuteIfBound("Delegate1Param!");
    	Delegate5Param.ExecuteIfBound("1Param!", "2Param!", "3Param!", "4Param!", "5Param!");
    	FString CurRV = DelegateRV.Execute();
    	PrintScreenStr(CurRV);
    
    	 DelegateLambda.ExecuteIfBound("DelegateLambda!");
    	 DelegateRaw.ExecuteIfBound("DelegateRaw!");
    	 DelegateSP.ExecuteIfBound("DelegateSP!");
    	 DelegateStatic.ExecuteIfBound("DelegateStatic!");
    	 DelegateUFunction.ExecuteIfBound("DelegateUFunction!");
    
    	 //执行多播
    	 DelegateMulticastD_NoParam.Broadcast();
    	 DelegateMulticastD_1Param.Broadcast(FString("@1Param!!!"));
    	
    }
    void AMyTPCharacter::FuncNoParam()
    {
    	PrintScreenStr("NoParam");
    }
    void AMyTPCharacter::Func1Param(FString Str)
    {
    	PrintScreenStr(Str);
    }
    void AMyTPCharacter::Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5)
    {
    	PrintScreenStr(Str1 + " "+ Str2 + " " + Str3 +" "+ Str4 + " " + Str5);
    }
    FString AMyTPCharacter::FuncRV()
    {
    	return  FString("FuncRV");
    }
    
    void AMyTPCharacter::TestUFunc(FString str)
    {
    	if (GEngine)
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, str);
    	}
    }
    
    
    
    
    
    void AMyTPCharacter::OnResetVR()
    {
    	UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
    }
    
    
    
    void AMyTPCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
    {
    		Jump();
    }
    
    void AMyTPCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
    {
    		StopJumping();
    }
    
    void AMyTPCharacter::TurnAtRate(float Rate)
    {
    	// calculate delta for this frame from the rate information
    	AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
    }
    
    void AMyTPCharacter::LookUpAtRate(float Rate)
    {
    	// calculate delta for this frame from the rate information
    	AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
    }
    
    void AMyTPCharacter::MoveForward(float Value)
    {
    	if ((Controller != NULL) && (Value != 0.0f))
    	{
    		// find out which way is forward
    		const FRotator Rotation = Controller->GetControlRotation();
    		const FRotator YawRotation(0, Rotation.Yaw, 0);
    
    		// get forward vector
    		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
    		AddMovementInput(Direction, Value);
    	}
    }
    
    void AMyTPCharacter::MoveRight(float Value)
    {
    	if ( (Controller != NULL) && (Value != 0.0f) )
    	{
    		// find out which way is right
    		const FRotator Rotation = Controller->GetControlRotation();
    		const FRotator YawRotation(0, Rotation.Yaw, 0);
    	
    		// get right vector 
    		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
    		// add movement in that direction
    		AddMovementInput(Direction, Value);
    	}
    }
    

    编辑器分别创建继承于TestActor、TestActor2的蓝图类TestA、TestA2,并且把它们摆放到测试场景关卡生成;

    启动游戏后,摁Q键,运行效果如下:

     

    4、动态多播代理委托(与蓝图混合使用)

    动态委托可以序列化,它们的函数可以通过名称找到,而且它们比常规委托慢。动态代理的使用方法和普通代理相似,不过动态多播代理可以暴露给蓝图绑定,方便在蓝图做处理,特别是C++和UMG数据通知交互。

    还是基于步骤1/2/3来实现,代码过程如下:

    MyTPCharacter.h:

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Character.h"
    #include "MyTest.h"
    #include "MyTPCharacter.generated.h"
    
    
    
    //声明无参代理
    DECLARE_DELEGATE(D_NoParam)
    //声明1个参数的代理
    DECLARE_DELEGATE_OneParam(D_1Param, FString)
    //声明5个参数的代理
    DECLARE_DELEGATE_FiveParams(D_5Param, FString, FString, FString, FString, FString)
    //返回值代理
    DECLARE_DELEGATE_RetVal(FString, D_RV)
    
    static void Static_Func(FString Str)
    {
    	if (GEngine)
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, Str);
    	}
    }
    
    //声明多播委托,与单播委托一样支持多参数传入,动态多播代理的名称开头须为F,否则会编译报错
    DECLARE_MULTICAST_DELEGATE(FMulticastD_NoParam);//声明无参数多播 
    DECLARE_MULTICAST_DELEGATE_OneParam(FMulticastD_1Param, FString);//声明一个参数多播
    
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDynamicMulticastD_1Param, FString, Str);//声明一个参数动态多播,动态多播代理的名称开头须为F,否则会编译报错
    
    UCLASS(config=Game)
    class AMyTPCharacter : public ACharacter
    {
    	GENERATED_BODY()
    
    	/** Camera boom positioning the camera behind the character */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    	class USpringArmComponent* CameraBoom;
    
    	/** Follow camera */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    	class UCameraComponent* FollowCamera;
    public:
    	AMyTPCharacter();
    
    	/** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
    	float BaseTurnRate;
    
    	/** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
    	float BaseLookUpRate;
    
    
    	//定义代理
    	D_NoParam DelegateNoParam;
    	D_1Param Delegate1Param;
    	D_5Param Delegate5Param;
    	D_RV DelegateRV;
    
    	//绑定到代理的函数
    	void FuncNoParam();
    	void Func1Param(FString Str);
    	void Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5);
    	FString FuncRV();
    
    	void OnRunEvent();//绑定到Q键函数,执行所有代理
    	void PrintScreenStr(FString Str);//打印日志到屏幕函数
    
    	//定义多种绑定
    	D_1Param DelegateLambda;
    	D_1Param DelegateRaw;
    	D_1Param DelegateSP;
    	D_1Param DelegateStatic;
    	D_1Param DelegateUFunction;
    
    	UFUNCTION()//可以声明BlueprintCallable等其他标记
    		void TestUFunc(FString str);
    	MyTest MyT;
    	TSharedPtr<MyTest> MyT_SPtr;
    
    
    	//多播
    	FMulticastD_NoParam DelegateMulticastD_NoParam;
    	FMulticastD_1Param  DelegateMulticastD_1Param;
    
    	UPROPERTY(BlueprintAssignable)//可在蓝图上绑定分配
    	FDynamicMulticastD_1Param  DelegateDynamicMulticastD_1Param;//动态多播
    
    protected:
    
    	/** Resets HMD orientation in VR. */
    	void OnResetVR();
    
    	/** Called for forwards/backward input */
    	void MoveForward(float Value);
    
    	/** Called for side to side input */
    	void MoveRight(float Value);
    
    	/** 
    	 * Called via input to turn at a given rate. 
    	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
    	 */
    	void TurnAtRate(float Rate);
    
    	/**
    	 * Called via input to turn look up/down at a given rate. 
    	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
    	 */
    	void LookUpAtRate(float Rate);
    
    	/** Handler for when a touch input begins. */
    	void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);
    
    	/** Handler for when a touch input stops. */
    	void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);
    
    protected:
    	// APawn interface
    	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
    	// End of APawn interface
    
    public:
    	/** Returns CameraBoom subobject **/
    	FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
    	/** Returns FollowCamera subobject **/
    	FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
    };
    
    

    MyTPCharacter.cpp:

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
    #include "MyTPCharacter.h"
    #include "HeadMountedDisplayFunctionLibrary.h"
    #include "Camera/CameraComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "Components/InputComponent.h"
    #include "GameFramework/CharacterMovementComponent.h"
    #include "GameFramework/Controller.h"
    #include "GameFramework/SpringArmComponent.h"
    
    #include "Engine/GameEngine.h"
    #include "MyTest.h"
    
    //
    // AMyTPCharacter
    
    AMyTPCharacter::AMyTPCharacter()
    {
    	
    	// Set size for collision capsule
    	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
    
    	// set our turn rates for input
    	BaseTurnRate = 45.f;
    	BaseLookUpRate = 45.f;
    
    	// Don't rotate when the controller rotates. Let that just affect the camera.
    	bUseControllerRotationPitch = false;
    	bUseControllerRotationYaw = false;
    	bUseControllerRotationRoll = false;
    
    	// Configure character movement
    	GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...	
    	GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate
    	GetCharacterMovement()->JumpZVelocity = 600.f;
    	GetCharacterMovement()->AirControl = 0.2f;
    
    	// Create a camera boom (pulls in towards the player if there is a collision)
    	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    	CameraBoom->SetupAttachment(RootComponent);
    	CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character	
    	CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller
    
    	// Create a follow camera
    	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    	FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
    	FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
    
    	// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) 
    	// are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)
    
    
    	DelegateNoParam.BindUObject(this, &AMyTPCharacter::FuncNoParam);//this只能是继承自UObject的类,绑定继承UObject类的对象函数
    	Delegate1Param.BindUObject(this, &AMyTPCharacter::Func1Param);
    	Delegate5Param.BindUObject(this, &AMyTPCharacter::Func5Param);
    	DelegateRV.BindUObject(this, &AMyTPCharacter::FuncRV);
    
    
    	//Lambda表达式
    	auto LambdaFunc = [&](FString str)
    	{
    		if (GEngine) {
    			GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, str);
    		}
    	};
    	DelegateLambda.BindLambda(LambdaFunc);//绑定lambda表达式
    	DelegateRaw.BindRaw(&MyT, &MyTest::TestFunc);//绑定到一个原始的C++类函数绑定到一个原始的C++指针全局函数代理上。原始指针不使用任何引用,所以如果从代理的底层删除了该对象,那么调用它可能是不安全的。因此,当调用Execute()时一定要小心!
    
    	
    	MyT_SPtr = MakeShareable(new MyTest());
    	DelegateSP.BindSP(MyT_SPtr.ToSharedRef(), &MyTest::TestFunc);//绑定一个基于共享指针的成员函数代理。共享指针代理保持到您的对象的弱引用。您可以使用 ExecuteIfBound() 来调用它们。
    	DelegateStatic.BindStatic(Static_Func);//绑定全局静态函数
    	DelegateUFunction.BindUFunction(this,"TestUFunc");//绑定this指向对象类蓝图函数
    
    }
    
    //
    // Input
    
    void AMyTPCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
    {
    	// Set up gameplay key bindings
    	check(PlayerInputComponent);
    
    	PlayerInputComponent->BindAction("RunEvent", IE_Pressed, this, &AMyTPCharacter::OnRunEvent);
    
    
    
    	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
    	PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
    
    	PlayerInputComponent->BindAxis("MoveForward", this, &AMyTPCharacter::MoveForward);
    	PlayerInputComponent->BindAxis("MoveRight", this, &AMyTPCharacter::MoveRight);
    
    	// We have 2 versions of the rotation bindings to handle different kinds of devices differently
    	// "turn" handles devices that provide an absolute delta, such as a mouse.
    	// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
    	PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
    	PlayerInputComponent->BindAxis("TurnRate", this, &AMyTPCharacter::TurnAtRate);
    	PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
    	PlayerInputComponent->BindAxis("LookUpRate", this, &AMyTPCharacter::LookUpAtRate);
    
    	// handle touch devices
    	PlayerInputComponent->BindTouch(IE_Pressed, this, &AMyTPCharacter::TouchStarted);
    	PlayerInputComponent->BindTouch(IE_Released, this, &AMyTPCharacter::TouchStopped);
    
    	// VR headset functionality
    	PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &AMyTPCharacter::OnResetVR);
    
    	
    }
    
    
    void AMyTPCharacter::PrintScreenStr(FString Str)
    {
    	if (GEngine) 
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Red, Str);
    	}
    }
    
    void AMyTPCharacter::OnRunEvent()
    {
    	//执行单播
    	DelegateNoParam.ExecuteIfBound();
    	Delegate1Param.ExecuteIfBound("Delegate1Param!");
    	Delegate5Param.ExecuteIfBound("1Param!", "2Param!", "3Param!", "4Param!", "5Param!");
    	FString CurRV = DelegateRV.Execute();
    	PrintScreenStr(CurRV);
    
    	 DelegateLambda.ExecuteIfBound("DelegateLambda!");
    	 DelegateRaw.ExecuteIfBound("DelegateRaw!");
    	 DelegateSP.ExecuteIfBound("DelegateSP!");
    	 DelegateStatic.ExecuteIfBound("DelegateStatic!");
    	 DelegateUFunction.ExecuteIfBound("DelegateUFunction!");
    
    	 //执行多播
    	 DelegateMulticastD_NoParam.Broadcast();
    	 DelegateMulticastD_1Param.Broadcast(FString("@1Param!!!"));
    	 DelegateDynamicMulticastD_1Param.Broadcast(FString("@1DynamicMulticast!"));//执行动态多播
    }
    void AMyTPCharacter::FuncNoParam()
    {
    	PrintScreenStr("NoParam");
    }
    void AMyTPCharacter::Func1Param(FString Str)
    {
    	PrintScreenStr(Str);
    }
    void AMyTPCharacter::Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5)
    {
    	PrintScreenStr(Str1 + " "+ Str2 + " " + Str3 +" "+ Str4 + " " + Str5);
    }
    FString AMyTPCharacter::FuncRV()
    {
    	return  FString("FuncRV");
    }
    
    void AMyTPCharacter::TestUFunc(FString str)
    {
    	if (GEngine)
    	{
    		GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, str);
    	}
    }
    
    
    
    
    
    void AMyTPCharacter::OnResetVR()
    {
    	UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
    }
    
    
    
    void AMyTPCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
    {
    		Jump();
    }
    
    void AMyTPCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
    {
    		StopJumping();
    }
    
    void AMyTPCharacter::TurnAtRate(float Rate)
    {
    	// calculate delta for this frame from the rate information
    	AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
    }
    
    void AMyTPCharacter::LookUpAtRate(float Rate)
    {
    	// calculate delta for this frame from the rate information
    	AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
    }
    
    void AMyTPCharacter::MoveForward(float Value)
    {
    	if ((Controller != NULL) && (Value != 0.0f))
    	{
    		// find out which way is forward
    		const FRotator Rotation = Controller->GetControlRotation();
    		const FRotator YawRotation(0, Rotation.Yaw, 0);
    
    		// get forward vector
    		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
    		AddMovementInput(Direction, Value);
    	}
    }
    
    void AMyTPCharacter::MoveRight(float Value)
    {
    	if ( (Controller != NULL) && (Value != 0.0f) )
    	{
    		// find out which way is right
    		const FRotator Rotation = Controller->GetControlRotation();
    		const FRotator YawRotation(0, Rotation.Yaw, 0);
    	
    		// get right vector 
    		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
    		// add movement in that direction
    		AddMovementInput(Direction, Value);
    	}
    }
    

    UPROPERTY(BlueprintAssignable)//可在蓝图上绑定分配
        FDynamicMulticastD_1Param  DelegateDynamicMulticastD_1Param;//动态多播

    定义多播DelegateDynamicMulticastD_1Param在蓝图TestA、TestA1、ThirdPersonCharacter蓝图上绑定,如下图

    运行游戏,摁下Q键,运行效果如下:

     

    5、代理委托解绑

    UnBind():解除绑定该代理。

    Remove():
    将函数从这个多播代理的调用列表中移除(性能为O(N))。请注意代理的顺序可能不会被保留!


    RemoveAll():
    将所有函数从与特定UserObject绑定的多播代理的调用列表中移除。请注意代理的顺序可能不会被保留!
    RemoveAll()将会移除所有与提供的指针绑定的注册代理! 不与对象指针绑定的原始代理将不会被此函数移除!
     

    注意:代理委托绑定时候,绑定函数返回一个代理委托,可以在绑定注册代理委托对象类的EndPlay、Destroyed等结束函数解绑该代理委托。

     

    完毕!解释都在代码里看到,代理委托有点像软件设计模式中的“观察者模式(Observer)”的具体运用,类与类可以降低低耦合!

    展开全文
  • 视频现状现在视频播放的需求越来越常见,就和16年上半年的直播一样,似乎不加个视频已经不是个正常的APP了,连微信朋友圈都支持上传小视频,更别谈以视频为本命的一系列APP。视频方面主要是两块,一个是视频录制,这...

    视频现状

    现在视频播放的需求越来越常见,就和16年上半年的直播一样,似乎不加个视频已经不是个正常的APP了,连微信朋友圈都支持上传小视频,更别谈以视频为本命的一系列APP。
    视频方面主要是两块,一个是视频录制,这个已经翻过一篇比较全的文章,再加上google开源的 grafika ,可以在踩坑时减少很多障碍,不过录制这块适配是大问题,需要不断调整。
    另一个方面就是视频播放,这方面的轮子比上面录制就多太多了,无论是google(开源良心)的 ExoPlayer,以及b站的 ijkplayer,还是一些其他的,基本上满足了正常的需求。
    当然,今天这篇文章这两个并不是主角,在视频播放这种极其容易造成卡顿,跳帧等影响用户体验的需求上,如何优化体验是一件十分重要的事情。一般情况比较正常的就直接播放,一句设置数据源的代码了事。但是要为了用户体验考虑。纵观这些有视频功能的APP,主要分为两类,一种是直接下载然后再播放,比如微信,微信的小视频录制压缩比比较好,一个视频大概几百k,所以比较适合先全量下载,然后再播放的模式,另一种自然就是边播放边缓存,这是比较多的策略,大部分的视频都是比较大的,等全部下完,黄花菜都凉了。

    基本原理

    既然是采取边播放边缓存的策略,比较逗的方式就是一边正常的给videoview设置数据源,一边开一个线程去下载文件,下完后就可以使用本地缓存了,这个方式是比较逗的,相当于两份网络请求,大大的拖慢了用户体验。所以我们会想如何只有一份请求但是能够操作这些数据边读边写呢,这个就是 AndroidVideoCache 所做的事情。
    AndroidVideoCache 通过代理的策略实现一个中间层将我们的网络请求转移到本地实现的代理服务器上,这样我们真正请求的数据就会被代理拿到,这样代理一边向本地写入数据,一边根据我们需要的数据看是读网络数据还是读本地缓存数据再提供给我们,真正做到了数据的复用。
    这就和我们使用的抓包软件性质一样,上个原理图更清晰

    代理服务器策略

    从使用开始

    这里在如何使用上直接搬运作者自己的readme。
    首先AS用户一行代码在gradle中导包

    dependencies {
        compile 'com.danikula:videocache:2.6.4'
    }
    

    然后在全局初始化一个本地代理服务器,这里选择在Application的实现类中,至于这个类是干什么的,后面会详细分析

    public class App extends Application {
    
        private HttpProxyCacheServer proxy;
    
        public static HttpProxyCacheServer getProxy(Context context) {
            App app = (App) context.getApplicationContext();
            return app.proxy == null ? (app.proxy = app.newProxy()) : app.proxy;
        }
    
        private HttpProxyCacheServer newProxy() {
            return new HttpProxyCacheServer(this);
        }
    }
    

    有了代理服务器,我们就可以使用了,把自己的网络视频url用提供的方法替换成另一个URL

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
        HttpProxyCacheServer proxy = getProxy();
        String proxyUrl = proxy.getProxyUrl(VIDEO_URL);
        videoView.setVideoPath(proxyUrl);
    }
    

    这样就已经可以正常使用了,当然这个库提供了更多的可以自定义的地方,比如缓存的文件最大大小,以及文件个数,缓存采取的是LruCache的方法,对于老文件在达到上限后会自动清理。

    private HttpProxyCacheServer newProxy() {
        return new HttpProxyCacheServer.Builder(this)
                .maxCacheSize(1024 * 1024 * 1024)       // 1 Gb for cache
                .build();
    }
    
    private HttpProxyCacheServer newProxy() {
        return new HttpProxyCacheServer.Builder(this)
                .maxCacheFilesCount(20)
                .build();
    }
    

    除了这个,还有一个就是生成的文件名,默认是使用的MD5方式生成key,考虑到一些业务逻辑,我们也可以继承一个 FileNameGenerator 来实现自己的策略

    public class MyFileNameGenerator implements FileNameGenerator {
    
        // Urls contain mutable parts (parameter 'sessionToken') and stable video's id (parameter 'videoId').
        // e. g. http://example.com?videoId=abcqaz&sessionToken=xyz987
        public String generate(String url) {
            Uri uri = Uri.parse(url);
            String videoId = uri.getQueryParameter("videoId");
            return videoId + ".mp4";
        }
    }
    
    ...
    HttpProxyCacheServer proxy = HttpProxyCacheServer.Builder(context)
        .fileNameGenerator(new MyFileNameGenerator())
        .build()
    

    到这里基本上整个AndroidVideoCache的使用就没什么问题了。

    具体分析

    知道了怎么使用后,我们继续往下走,看看是怎么实现的,这里就不分析后面的那些LruCache这些缓存策略,生成key之类的逻辑了,和一般的网络请求里的都大同小异,我们直接看这个代码最有含金量的地方。
    前面在使用中,全局实例化过一个代理服务器,就先从这里开始

    HttpProxyCacheServer.java
        private static final String PROXY_HOST = "127.0.0.1";
        private final Object clientsLock = new Object();
        private final ExecutorService socketProcessor = Executors.newFixedThreadPool(8);
        private final Map<String, HttpProxyCacheServerClients> clientsMap = new ConcurrentHashMap<>();
        private final ServerSocket serverSocket;
        private final int port;
        private final Thread waitConnectionThread;
        private final Config config;
        private final Pinger pinger;
    
        private HttpProxyCacheServer(Config config) {
            this.config = checkNotNull(config);
            try {
                InetAddress inetAddress = InetAddress.getByName(PROXY_HOST);
                this.serverSocket = new ServerSocket(0, 8, inetAddress);
                this.port = serverSocket.getLocalPort();
                CountDownLatch startSignal = new CountDownLatch(1);
                this.waitConnectionThread = new Thread(new WaitRequestsRunnable(startSignal));
                this.waitConnectionThread.start();
                startSignal.await(); // freeze thread, wait for server starts
                this.pinger = new Pinger(PROXY_HOST, port);
                LOG.info("Proxy cache server started. Is it alive? " + isAlive());
            } catch (IOException | InterruptedException e) {
                socketProcessor.shutdown();
                throw new IllegalStateException("Error starting local proxy server", e);
            }
        }
    

    这个构造函数一眼看过去就很清楚了,参数就是前面那些自定义配置,这里使用的是 127.0.0.1,这个就是localhost的ip也就是本地ip,创建了一个 ServerSocket ,随机分配了一个端口,这里通过 getLocalPort 拿到了这个服务器端口,后面用来通信。
    这里出现了一个线程 WaitRequestsRunnable 并且调用了 start 方法,继续跟进去看这个线程

            @Override
            public void run() {
                startSignal.countDown();
                waitForRequest();
            }
    

    信号量主要是为了保证这个run方法先执行,继续看这个 waitForRequest 方法

        private void waitForRequest() {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    Socket socket = serverSocket.accept();
                    LOG.debug("Accept new socket " + socket);
                    socketProcessor.submit(new SocketProcessorRunnable(socket));
                }
            } catch (IOException e) {
                onError(new ProxyCacheException("Error during waiting connection", e));
            }
        }
    

    好了,到这里服务器socket一套比较清晰了,整理一下就是先构建一个全局的一个本地代理服务器 ServerSocket,指定一个随机端口,然后新开一个线程,在线程的 run 方法里,通过accept() 方法监听这个服务器socket的入站连接,accept() 方法会一直阻塞,直到有一个客户端尝试建立连接。
    现在有了服务器,然后就是客户端的socket,先从使用时代理替换url地方开始看

        HttpProxyCacheServer proxy = getProxy();
        String proxyUrl = proxy.getProxyUrl(VIDEO_URL);
    

    这里使用的是HttpProxyCacheServer 中的 getProxyUrl 方法

        public String getProxyUrl(String url, boolean allowCachedFileUri) {
            if (allowCachedFileUri && isCached(url)) {
                File cacheFile = getCacheFile(url);
                touchFileSafely(cacheFile);
                return Uri.fromFile(cacheFile).toString();
            }
            return isAlive() ? appendToProxyUrl(url) : url;
        }
    

    整个策略就是如果本地已经缓存了,就直接那本地地址的Uri,并且touch一下文件,把时间更新后最新,因为后面LruCache是根据文件被访问的时间进行排序的,如果文件没有被缓存那么就会先走一下 isAlive() 方法, 这里会ping一下目标url,确保url是一个有效的,如果用户是通过代理访问的话,就会ping不通,这样就还是原生url,正常情况都会进入这个 appendToProxyUrl 方法里面。

        private String appendToProxyUrl(String url) {
            return String.format(Locale.US, "http://%s:%d/%s", PROXY_HOST, port, ProxyCacheUtils.encode(url));
        }
    

    比较直接,这里拼接出来一个带有127.0.0.1目标地址及端口并携带原url的新地址,这个请求的话就会被我们的服务器socket监听到,也就是前面的accept() 会继续往下走,这里接收到的socket就是我们所请求的客户端socket

     socketProcessor.submit(new SocketProcessorRunnable(socket));
    

    整个socket会被包裹成一个runnable,发配给线程池。这个 runnable 的 run 方法中所做的事情就是调用了一个方法

        private void processSocket(Socket socket) {
            try {
                GetRequest request = GetRequest.read(socket.getInputStream());
                LOG.debug("Request to cache proxy:" + request);
                String url = ProxyCacheUtils.decode(request.uri);
                if (pinger.isPingRequest(url)) {
                    pinger.responseToPing(socket);
                } else {
                    HttpProxyCacheServerClients clients = getClients(url);
                    clients.processRequest(request, socket);
                }
            } catch (SocketException e) {
                // There is no way to determine that client closed connection http://stackoverflow.com/a/10241044/999458
                // So just to prevent log flooding don't log stacktrace
                LOG.debug("Closing socket… Socket is closed by client.");
            } catch (ProxyCacheException | IOException e) {
                onError(new ProxyCacheException("Error processing request", e));
            } finally {
                releaseSocket(socket);
                LOG.debug("Opened connections: " + getClientsCount());
            }
        }
    

    前面ping的过程其实也被会这个socket监听并且走进来这一段,不过这个比较简单,就不分析了,我们直接看里面的 else 框内的代码,这里一个 getClients 就是一个ConcurrentHashMap,重复url返回的是同一个HttpProxyCacheServerClients ,

     HttpProxyCacheServerClients clients = clientsMap.get(url);
                if (clients == null) {
                    clients = new HttpProxyCacheServerClients(url, config);
                    clientsMap.put(url, clients);
                }
                return clients;
    

    如果是第一次就会根据url构建出一个HttpProxyCacheServerClients并被put到ConcurrentHashMap中,真正的操作都在这个客户端的 processRequest 操作中,并且传递过去一个是request,这是一个GetRequest 对象,是一个url和rangeoffset以及partial的包装类,另一个就是客户端socket。

        public void processRequest(GetRequest request, Socket socket) throws ProxyCacheException, IOException {
            startProcessRequest();
            try {
                clientsCount.incrementAndGet();
                proxyCache.processRequest(request, socket);
            } finally {
                finishProcessRequest();
            }
        }
    

    这里 startProcessRequest 方法会得到一个HttpProxyCache 类

        private synchronized void startProcessRequest() throws ProxyCacheException {
            proxyCache = proxyCache == null ? newHttpProxyCache() : proxyCache;
        }
    
        private HttpProxyCache newHttpProxyCache() throws ProxyCacheException {
            HttpUrlSource source = new HttpUrlSource(url, config.sourceInfoStorage);
            FileCache cache = new FileCache(config.generateCacheFile(url), config.diskUsage);
            HttpProxyCache httpProxyCache = new HttpProxyCache(source, cache);
            httpProxyCache.registerCacheListener(uiCacheListener);
            return httpProxyCache;
        }
    

    在这里,我们构建一个基于原生url的HttpUrlSource ,这个类负责持有url,并开启HttpURLConnection来获取一个InputStream,这样才能通过这个输入流读数据,同时也创建了一个本地的临时文件,一个以.download结尾的临时文件,这个文件在成功下载完后的 FileCache 类中的 complete 方法中被更名。
    我们构建了一个HttpProxyCache 类,也注册了一个CacheListener,这个listener可以用来回调进度。
    做完这一切之后,然后这个HttpProxyCache 对象就开始 processRequest

        public void processRequest(GetRequest request, Socket socket) throws IOException, ProxyCacheException {
            OutputStream out = new BufferedOutputStream(socket.getOutputStream());
            String responseHeaders = newResponseHeaders(request);
            out.write(responseHeaders.getBytes("UTF-8"));
    
            long offset = request.rangeOffset;
            if (isUseCache(request)) {
                responseWithCache(out, offset);
            } else {
                responseWithoutCache(out, offset);
            }
        }
    

    这里我们用传过来的那个客户端socket,拿到一个OutputStream输出流,这样我们就能往里面写数据了,如果不用缓存就走常规逻辑,这里我们只看走缓存的行为。

        private void responseWithCache(OutputStream out, long offset) throws ProxyCacheException, IOException {
            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
            int readBytes;
            while ((readBytes = read(buffer, offset, buffer.length)) != -1) {
                out.write(buffer, 0, readBytes);
                offset += readBytes;
            }
            out.flush();
        }
    

    构造一个8 * 1024字节的buffer,这里的read方法,实际上是调用的父类ProxyCache的实现

        public int read(byte[] buffer, long offset, int length) throws ProxyCacheException {
            ProxyCacheUtils.assertBuffer(buffer, offset, length);
    
            while (!cache.isCompleted() && cache.available() < (offset + length) && !stopped) {
                readSourceAsync();
                waitForSourceData();
                checkReadSourceErrorsCount();
            }
            int read = cache.read(buffer, offset, length);
            if (cache.isCompleted() && percentsAvailable != 100) {
                percentsAvailable = 100;
                onCachePercentsAvailableChanged(100);
            }
            return read;
        }
    

    在while循环里面,开启了一个新的线程sourceReaderThread,其中封装了一个SourceReaderRunnable的Runnable,这个异步线程用来给cache,也就是本地文件写数据,同时还更新一下当前的缓存进度

            int sourceAvailable = -1;
            int offset = 0;
            try {
                offset = cache.available();
                source.open(offset);
                sourceAvailable = source.length();
                byte[] buffer = new byte[ProxyCacheUtils.DEFAULT_BUFFER_SIZE];
                int readBytes;
                while ((readBytes = source.read(buffer)) != -1) {
                    synchronized (stopLock) {
                        if (isStopped()) {
                            return;
                        }
                        cache.append(buffer, readBytes);
                    }
                    offset += readBytes;
                    notifyNewCacheDataAvailable(offset, sourceAvailable);
                }
                tryComplete();
                onSourceRead();
    

    同时我们的另一个线程也会从cache中去读数据,在缓存结束后同样也会发送一个通知通知自己已经缓存完了,回调由外界控制。
    以上差不多就是总体代码,这里我们在请求远程URL时将文件写到本地fileCache中,然后读数据从本地读取,写入到客户端socket里面,服务器Socket主要还是一个代理的作用,从中间拦截掉网络请求,然后实现对socket的读取和写入。

    后记

    整个分析为了节约篇幅,尽量的是描述一些其中比较重要的片段,源码文件还是比较多的,这里不能详述,对这种代理方式感兴趣的可以在自己详细阅读一下源码,毕竟源码面前,了无秘密。
    这个项目用起来有一点问题,是因为如果我们的APP设置了代理,那么这个socket方式拿url就会出问题,因为我们拿到的也是一个代理url,所以在开发时需要考虑代理用户提供兼容性处理。
    另外这种本地代理服务器的策略也能为我们提供一些不一样的思路,既然视频可行那么音频文件呢,进而推导到普通的网络请求,json文件。基于这样一套思路,在其基础上甚至能够实现一套离线缓存加载的策略,当然这取决于我们自身的服务器架构,服务端URL策略。



    作者:sheepm
    链接:https://www.jianshu.com/p/4745de02dcdc
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
    展开全文
  • nginx代理视频播放响应时间长问题

    千次阅读 2017-12-18 20:10:57
    通过查看nginx日志发现存在大量相同的重复请求,通过分析该批视频都是用户上传的1080P的视频,而由于安卓播放器ijkplayer对1080P的播放出现异常导致播放器不断的重复发起播放请求,导致nginx的连接被不断占用,最后...
  • 移动端视频播放插件

    2018-03-15 13:33:58
    移动端视频播放插件移动端视频播放插件移动端视频播放插件移动端视频播放插件移动端视频播放插件
  • Gif动画不是视频,可以暂停和继续播放,但是我们可以取出Gif图中的其中一帧作为静态图片,然后利用JavaScript模拟暂停和播放。今天分享的jQuery插件就是帮助你完成这个功能,它的实现原理很简单,暂停Gif动画图时将...
  • javascript 视频播放

    2013-12-06 16:26:38
    一个简单易懂的js视频播放demo页面.
  • gifplayer是一款可以控制GIF动画格式图片播放和暂停的jQuery插件。该jQuery插件使用简单,文件体积小。可用于控制任何GIF动画图片的开始播放和暂停播放
  • 上海期货交易所(下文简称:...缩以及组传输等技术方法,发送期货及衍生品交易行情。 本规范介绍上期所第二行情发布平台(下文简称:平台)提供的服务,以及服务接口、协议和编码,并给出从平台获取 行情的步骤。
  • 高并发下的Nginx性能优化实战

    万人学习 2019-12-24 14:44:52
    【超实用课程内容】 本课程内容包含讲解解读Nginx的基础知识,解读Nginx的...你可以根据所学知识,自行修改、优化 下载方式:电脑登录https://edu.csdn.net/course/detail/27216,播放页面右侧点击课件进行资料打包下载
  • 播放HDR视频需要注意什么

    千次阅读 2019-04-08 13:46:09
    最近两年HDR这个概念可谓是铺天盖地而来,手机也好PC也好电视也好,都拼命往自己头上扣HDR的帽子。而在某些发烧友眼中,如果看片子不带HDR,堪比...PC并不是专门为视频播放设计的机器,和专业的蓝光机等播放器相比...
  • 轻松学,Java 中的代理模式及动态代理

    万次阅读 多人点赞 2017-06-29 22:08:55
    电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告。 现在用代码来进行模拟。 首先得有一个接口,通用的...
  • 直播运营服务合同.docx
  • 直播电商系列3—运营:求变与新机,善营销者得未来.pdf
  • UE4 C++ —— 代理

    千次阅读 2018-09-15 20:07:43
    UE4提供了三种类型的代理,单代理,多代理,动态代理。单代理 宏 详解 DECLARE_DELEGATE( DelegateName ) 无参无返回值 DECLARE_DELEGATE_OneParam(DelegateName, Param1Type ) 一...
  • 优化[过滤]理、会员逻辑,观看扣次数逻辑。 修复评分刷积分。 修复开屏广告开关、播放器暂停广告。 修复视频标签。 修复切换播放源闪退的情况。 修复加载失败的一种闪退情况 注:有教程+视频说明,有兴趣的自己...
  • 什么是

    千次阅读 2017-03-10 13:58:01
    协议允许将一台主机发送的数据通过网络路由器和交换机复制到多个加入此组的主机,是一种一对多...组协议与现在广泛使用的单协议的不同之处在于,一个主机用单协议向n个主机发送相同的数据时,发送主机需
  • 【UE4】多代理的使用

    千次阅读 2018-06-15 17:08:26
    代理多代理的功能和单代理几乎一样。区别是它们对目标为弱引用,可以和结构体一起使用,可以很方便地进行拷贝,等等。 和普通代理一样,多代理可被载入/保存,并远程触发;但是,多代理的函数无法使用...
  • VLC使用代理服务器播放

    千次阅读 2009-02-13 09:11:00
    使用代理服务器播放时,使用如下命令打开播放器 vlc --http-proxy=http://10.10.1.2:80 代理服务器IP:端口号或者,定义环境变量变量:http-proxy=http://10.10.1.2:80则直接打开vlc即可。
  •  iOS视频边下边,使用系统自带的方式实现 实现方案: 1.需要在视频播放器和服务器之间添加一层类似代理的机制,视频播放器不再直接访问服务器,而是访问代理对象,代理对象去访问服务器获得数据,之后返回给视频...
  • 播放4K视频需要什么样的配置

    千次阅读 2020-01-08 19:19:21
    CPU:9英特尔酷睿 i5,4核心,8MB缓存; 显卡:大于2GB显存,如NVIDIA® GeForce® GTX 1650 4GB 磁盘:因为要告诉读取数据,低速磁盘会导致视频卡顿,建议使用固态硬盘或告诉硬盘。 这个配置 是目前主流的...
  • 基于嵌入式Linux的H264视频播放系统设计[摘要]随着第三移动通信技术的逐步推广应用,将移动流媒体技术引入移动增值业务,已成为目前全球范围内移动业务应用研究的热点之一。而高效、实用的多媒体终端设备融合了...
  • ![图片说明](https://img-ask.csdn.net/upload/201711/27/1511770165_370863.png) ![图片说明]... 如图,视频可以播放,可是一点进度条就重新播放,该怎么解决
  • RGB裸数据播放软件

    2018-07-26 09:20:43
    这是能播放RGB裸数据的播放软件,其名为vooya.exe,在播放时需要选择代播放视频的长宽信息
  • 基于单片机的DF100A短波发射机远程自动代播系统硬件设计.pdf
  • jQuery Gif图片播放和暂停插件是一款使用一张静态的图片作为GIF图片的预览图,然后在用户点击播放按钮时才开始GIF动画序列的播放

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 97,000
精华内容 38,800
关键字:

代播是什么